1 表单基础(acceptCharset / action / elements / enctype / length / method / name / reset / submit / target 等)
概念 :这些是 <form>
的属性或与表单相关的 DOM 属性/方法,用于定义提交目标、编码方式、提交方法以及通过脚本访问表单控件等。
原理:
action
:提交目标 URL(表单数据要发送到哪里)。method
:提交方法(GET
、POST
)。GET
把数据放在查询字符串,POST
放在请求体(适合大数据或文件)。enctype
:提交数据的编码类型。常见application/x-www-form-urlencoded
(默认)、multipart/form-data
(上传文件时)和text/plain
。accept-charset
:告知服务器哪种字符集被用于表单数据(通常使用UTF-8
)。target
:提交后的显示目标(例如_self
,_blank
, iframe 名称)。name
:表单的名字(旧时常用,可在window
上形成全局引用,但现在推荐id
)。elements
(DOM 属性):表单控件集合(不是 HTML 属性),可通过form.elements
访问按索引或按控件name
。length
(DOM 属性): 表示form.elements.length
(控件数量)。submit
/reset
:既是按钮类型(<input type="submit">
)也是表单方法(form.submit()
、form.reset()
)。
对比:
form.submit()
(程序化提交) vs 用户点击提交按钮:form.submit()
跳过submit
事件的触发与约束验证(注意:requestSubmit()
是触发验证并触发submit
事件的较新方法)。enctype="multipart/form-data"
必须和input[type="file"]
一起使用来上传文件;否则二进制文件不能正常发送。
实践(示例) :
xml
<!-- 示例表单(逐行注释) -->
<form id="demoForm"
action="/submit" <!-- 表单要发往的 URL -->
method="POST" <!-- 使用 POST 提交数据 -->
enctype="multipart/form-data" <!-- 用于上传文件 -->
accept-charset="UTF-8" <!-- 告知字符编码 -->
target="_self" <!-- 提交后在当前窗口打开结果 -->
novalidate <!-- 临时禁用 HTML5 自动验证(可选) -->
>
<!-- 文件输入 -->
<input type="file" name="avatar" />
<!-- 文本输入 -->
<input type="text" name="username" />
<!-- 提交按钮 -->
<button type="submit">提交</button>
<!-- 重置按钮 -->
<button type="reset">重置</button>
</form>
<script>
// JS:访问表单、elements、length
const form = document.getElementById('demoForm'); // 获取表单 DOM
console.log('控件数量(form.elements.length):', form.elements.length); // length 属性
console.log('通过 name 访问 username:', form.elements['username']); // 按 name 获取控件
// programmatic submit 注意事项(见下)
// form.submit(); // 直接提交(不会触发 submit 事件也不会触发验证)
// 推荐:form.requestSubmit()(如果浏览器支持)会触发验证和 submit 事件
</script>
拓展:
form.enctype
与FormData
:在使用fetch
+FormData
时,浏览器会自动设置Content-Type: multipart/form-data
边界(boundary)。target
可以指向 iframe 名称以在页面内嵌显示结果。
潜在问题:
form.submit()
跳过submit
事件与约束验证(可能导致不受控提交)。现代推荐使用requestSubmit()
或触发按钮的click()
。- 使用
accept-charset
多数情况下无需手动设置,只要服务端与页面使用 UTF-8 即可。
2 提交表单(submit)
概念 :表单提交可以由用户点击提交按钮触发,也可由脚本触发(form.submit()
、form.requestSubmit()
)。提交前浏览器会执行约束验证(默认情况下)。
原理:
- 用户触发(例如
<button type="submit">
)会触发submit
事件,事件处理器preventDefault()
可阻止默认提交。 form.submit()
:程序化提交,不触发submit
事件,也不执行约束验证。form.requestSubmit()
:较新的 API,会模拟用户提交(会执行约束验证并触发 submit 事件),可以传入触发提交的按钮以保留其formmethod
等属性。
对比:
form.submit()
:快速直接,但跳过验证与事件。requestSubmit()
:更"语义正确",会触发验证与事件(如果浏览器支持);兼容性较新,但主流现代浏览器已支持。
实践(示例并注释) :
xml
<form id="f1">
<input name="email" type="email" required />
<button id="btn" type="submit">送出</button>
</form>
<script>
const f = document.getElementById('f1');
// 拦截 submit 事件,做自定义处理:
f.addEventListener('submit', function (e) {
e.preventDefault(); // 阻止默认的表单提交(便于做 AJAX)
// 验证可用 checkValidity() / reportValidity()
if (!f.checkValidity()) {
// 报告并显示浏览器内置提示
f.reportValidity();
return;
}
// 继续:可以使用 fetch 提交或者 f.submit() 提交
// 例如将数据收集后用 fetch 发送:
const fd = new FormData(f);
// fetch('/submit', { method: 'POST', body: fd }) ...
console.log('准备通过 fetch 提交', [...fd.entries()]);
});
// 程序化提交示例(推荐使用 requestSubmit)
document.getElementById('btn').addEventListener('click', () => {
// f.requestSubmit(); // 如果浏览器支持,会触发验证与submit事件
// 兼容写法:若不支持 requestSubmit,可创建隐藏按钮并 click()
if (typeof f.requestSubmit === 'function') {
f.requestSubmit();
} else {
// fallback:创建临时 submit 按钮并点击
const tmp = document.createElement('button');
tmp.type = 'submit';
tmp.style.display = 'none';
f.appendChild(tmp);
tmp.click();
f.removeChild(tmp);
}
});
</script>
拓展:
formdata
事件(FormDataEvent
)可在提交前操作FormData
(某些场景)。- 使用
fetch
+FormData
完全替代表单提交,便于 SPA 异步提交。
潜在问题:
- 忽略验证(调用
form.submit()
)会让无效数据提交。 - 老旧浏览器可能不支持
requestSubmit()
,需 fallback。
3 重置表单(reset)
概念 :form.reset()
将表单控件的当前值恢复到初始(页面加载或最后一次设置默认值)状态。<button type="reset">
触发同样的行为。
原理:
- 每个控件有
.defaultValue
/.defaultChecked
属性,reset()
会把value
/checked
恢复为.defaultValue
/.defaultChecked
。
实践(示例并注释) :
xml
<form id="fReset">
<input name="name" value="初始名" />
<input type="checkbox" name="agree" checked />
<button type="reset">重置</button>
</form>
<script>
const fr = document.getElementById('fReset');
// 程序化重置:
// fr.reset(); // 会把控件 value/checked 恢复为其 default 值
// 示例:改变值后再 reset 可看到恢复
fr.elements['name'].value = '改变后的值';
console.log('改变后的值:', fr.elements['name'].value); // 改变后
// fr.reset(); // 恢复到 "初始名"
</script>
拓展:
- 可在
reset
事件上拦截并做提示或阻止(例如e.preventDefault()
),然后进行自定义行为。
潜在问题:
reset()
不会清除defaultValue
,只能恢复到 default。如果需要清空表单,应手动设置value = ''
。
4 表单字段(使用 form.elements
访问 name 对应的值)
概念 :form.elements
返回 HTMLFormControlsCollection,按索引、按 name
或 id
访问。也可使用 new FormData(form)
快速读取所有可提交字段。
原理:
form.elements['foo']
若有多个同名控件会返回集合(HTMLCollection-like),单个 name 返回元素对象。FormData
会收集控件的值(包括文件)并可迭代。
实践(示例并注释) :
xml
<form id="fElements">
<input name="username" value="alice" />
<input name="hobby" value="reading" />
<input name="hobby" value="coding" />
</form>
<script>
const f = document.getElementById('fElements');
// 通过 form.elements 访问单个控件
const usernameInput = f.elements['username']; // 直接得到 input 元素
console.log('username value:', usernameInput.value); // alice
// 同名控件 hobby:会返回 HTMLCollection(多个选项)
const hobbies = f.elements['hobby'];
console.log('hobby 类型:', Object.prototype.toString.call(hobbies)); // NodeList 或 HTMLCollection
// 如果要把同名输入的值组合为数组:
const hobbyValues = Array.from(f.elements['hobby']).map(i => i.value);
console.log('hobby values:', hobbyValues);
// 使用 FormData 读取当前可提交的数据(推荐)
const fd = new FormData(f);
for (const [k, v] of fd.entries()) {
console.log(k, v);
}
</script>
拓展:
FormData
可用于fetch
:fetch('/api', { method: 'POST', body: new FormData(f) })
。Object.fromEntries(new FormData(f))
可快速构造纯 JS 对象(注意多值同名字段会只保留最后一个值,或需特殊处理)。
潜在问题:
- 使用
form.elements[name]
时如果name
与 window 全局变量冲突会出问题;尽量使用id
+querySelector
或FormData
来避免歧义。
5 表单验证(使用 form.checkValidity()
)
概念 :HTML5 约束验证 API(Constraint Validation API)允许以声明式(required
、pattern
、type="email"
等)或编程方式(checkValidity()
、setCustomValidity()
、reportValidity()
)进行验证。
原理:
element.checkValidity()
:返回布尔值,表示该控件是否满足约束;若不满足,会触发该控件的invalid
事件(可监听)。form.checkValidity()
:遍历表单控件,若全部通过返回true
,否则返回false
(并会触发控件的invalid
事件)。element.setCustomValidity(msg)
:设置自定义错误信息,非空字符串使控件无效(checkValidity()
返回 false),空字符串恢复有效。reportValidity()
:会显示浏览器的内置提示并返回验证结果(等同checkValidity()
+ 展示消息)。
注:浏览器行为细节随现代浏览器发展略有差异,但上述 API 是规范行为。
5.1 表单中的公共属性(disabled
form
name
readOnly
tabIndex
type
value
)
概念与实践(举例并注释):
xml
<input id="ex" name="ex" type="text" value="初始" readonly tabindex="5" />
<!-- 属性说明:
- disabled: 控件禁用(不可交互,提交时不会包含在表单数据中)
- form: 指定所属表单的 id(用于把控件放在表单外仍归属某个表单)
- name: 提交 key
- readOnly: 只读(可聚焦、不可编辑)
- tabIndex: 控制 tab 键聚焦顺序(-1 表示不可通过 tab 获得)
- type/value: 控件类型与当前值 -->
拓展:
disabled
与readOnly
的区别:disabled
通常不可聚焦且不会提交;readOnly
可聚焦、可复制但不可编辑,会提交。
5.2 表单字段的公共方法(focus
blur
)
概念 :element.focus()
将焦点移动到控件上;element.blur()
使控件失去焦点。常用于表单自动聚焦、错误后聚焦等。
实践:
xml
<input id="email" />
<script>
const e = document.getElementById('email');
e.focus(); // 聚焦到 email 输入框
// 模拟延迟失焦(示例)
setTimeout(() => e.blur(), 2000); // 2 秒后失去焦点
</script>
潜在问题:
- 在不可见或 display:none 元素上调用
focus()
可能无效或抛异常;确保元素可见并可聚焦。
5.3 表单字段的公共事件(change
input
invalid
reset
select
submit
blur
)
概念:
input
:实时输入时触发(任何字符变更)。change
:控件值"改变并失焦后"触发(对于select
则是选中即触发)。invalid
:控件验证失败时触发(可拦截)。reset
(表单):表单被 reset 时触发。select
:当文本被选中(input/textarea)触发。submit
:表单提交时触发(用户行为或requestSubmit()
会触发;form.submit()
不触发)。blur
:失去焦点时触发。
实践(示例) :
ini
<form id="evtForm">
<input id="i1" required />
<textarea id="ta"></textarea>
<button type="submit">发送</button>
<button type="reset">清除</button>
</form>
<script>
const f = document.getElementById('evtForm');
const i1 = document.getElementById('i1');
const ta = document.getElementById('ta');
i1.addEventListener('input', e => {
console.log('输入中:', e.target.value); // 实时输入
});
i1.addEventListener('change', e => {
console.log('change: 值已改变且失焦', e.target.value);
});
i1.addEventListener('invalid', e => {
e.preventDefault(); // 阻止默认提示,改为自定义提示
console.warn('输入无效:', e.target.validationMessage);
});
ta.addEventListener('select', e => {
// 当用户选中文本时触发(仅 input/textarea)
const start = e.target.selectionStart;
const end = e.target.selectionEnd;
console.log('选中文本:', e.target.value.slice(start, end));
});
f.addEventListener('reset', e => {
console.log('表单已重置');
});
f.addEventListener('submit', e => {
e.preventDefault();
console.log('submit 被捕获,可做异步提交');
});
</script>
潜在问题:
invalid
事件会在控件第一次无效时触发并可被阻止;如果想统一处理所有错误,需监听submit
并用checkValidity()
。
6 文本框编程(type="text"
)
概念 :<input type="text">
是最常见的单行文本输入控件,支持 maxlength
、placeholder
、pattern
、autocomplete
等属性。
实践:
ini
<input id="t" type="text" name="nick" placeholder="请输入昵称" maxlength="20" />
<script>
const t = document.getElementById('t');
console.log('当前值:', t.value);
// 设置默认值(也会同时改变 defaultValue)
t.defaultValue = '默认昵称';
</script>
拓展:
inputmode
可以建议软键盘布局(如numeric
),对移动端友好。autocomplete
控制是否允许浏览器自动填充。
7 选择文本(event.target.select()
)
概念:
- 在
<input>
或<textarea>
中,可使用element.select()
选中全部文本。 select
事件在选中文本时触发(input/textarea)。
实践(示例) :
typescript
<input id="selDemo" type="text" value="选我试试" />
<button id="doSelect">全选</button>
<script>
const inp = document.getElementById('selDemo');
document.getElementById('doSelect').addEventListener('click', () => {
inp.select(); // 选中 input 中所有文本
});
inp.addEventListener('select', e => {
// select 事件:可拿到 selectionStart / selectionEnd
const start = e.target.selectionStart;
const end = e.target.selectionEnd;
console.log('选中位置:', start, end);
console.log('选中文本:', e.target.value.slice(start, end));
});
</script>
7.2 取得选中文本(document.selection.createRange().text
与 event / selection API)
对比:
-
document.selection.createRange().text
:IE(<=8)专用旧 API,不再推荐。 -
现代浏览器:
- 对于
input
/textarea
:使用selectionStart
/selectionEnd
并截取value.slice(start,end)
。 - 对于
contenteditable
或普通页面文本:使用window.getSelection()
+Selection.toString()
。
- 对于
实践(示例) :
ini
// 输入框选中文本:
const el = document.querySelector('input');
const selected = el.value.slice(el.selectionStart, el.selectionEnd);
// contenteditable 或文档文本:
const sel = window.getSelection();
const text = sel.toString(); // 选中文本
7.3 部分选中文本(setSelectionRange(start, end)
)
实践:
ini
const el = document.querySelector('input');
el.setSelectionRange(2, 5); // 将光标/选择设为从索引 2 到 5(仅对可选择的 input/textarea)
el.focus(); // 通常需要先 focus 才能看到选择效果
潜在问题:
- 对于某些类型(如
type="number"
),部分浏览器可能不支持selectionStart/End
与setSelectionRange
,最佳实践是用type="text"
或在需要时临时切换。
8 输入过滤(概念与使用场景)
概念 :输入过滤指在用户输入时限制或清洗不合规字符(例如只允许数字、禁止表情、手机号码格式化等)。可以在 keydown
/keypress
阻止输入,也可以在 input
事件后修正值,或利用 pattern
做宣告式约束。
对比:
- 事件阻止(
keydown
/keypress
+preventDefault()
):可在输入发生前阻断,但键盘以外输入(粘贴、拖拽、输入法)仍需处理。 input
事件后替换值:更稳健,能覆盖粘贴/输入法场景,但用户会短暂看到不合规字符(可即时替换)。pattern
+title
:仅用于验证,不会阻止输入,但会在提交时提示。
8.1 屏蔽字符(示例使用事件阻止字符输入)
实践(示例:仅允许数字) :
typescript
<input id="numOnly" type="text" placeholder="只允许数字" />
<script>
const num = document.getElementById('numOnly');
// 推荐:在 input 事件中清理(覆盖所有输入方式)
num.addEventListener('input', (e) => {
// 只保留数字:用正则清理
const cleaned = e.target.value.replace(/\D+/g, '');
if (cleaned !== e.target.value) {
// 保持光标位置需要更复杂处理;这里简单替换
e.target.value = cleaned;
}
});
// 可选:在 keydown 阻止非数字键(兼顾用户体验)
num.addEventListener('keydown', e => {
// 允许控制键(Backspace, Delete, Arrow keys)
if (e.ctrlKey || e.metaKey || e.altKey) return;
const allowed = ['Backspace','ArrowLeft','ArrowRight','Delete','Tab'];
if (allowed.includes(e.key)) return;
if (!/^\d$/.test(e.key)) {
e.preventDefault(); // 阻止非数字按键
}
});
</script>
潜在问题:
- 输入法(IME)在 composition 阶段会触发多个事件,阻断键盘事件可能破坏中文输入体验。对中文/日文场景需特别处理(监听
compositionstart
/compositionend
)。
8.2 处理剪贴板(API、示例、浏览器兼容)
概念:
- 处理剪贴板可分为"响应粘贴事件处理粘贴内容"(
paste
事件 +event.clipboardData
)与"主动读取/写入剪贴板"(navigator.clipboard.readText()
/writeText()
,需要权限/HTTPS)。
实践(粘贴事件处理示例并注释) :
ini
<textarea id="pasteArea" placeholder="粘贴文本,会自动移除脚本标签"></textarea>
<script>
const ta = document.getElementById('pasteArea');
ta.addEventListener('paste', (e) => {
e.preventDefault(); // 阻止默认粘贴行为(我们将插入清理后的内容)
// event.clipboardData 在 paste 事件中可用
const text = (e.clipboardData || window.clipboardData).getData('text/plain');
// 简单清理示例:移除 <script> 标签,防止 XSS(生产中应更严谨使用白名单 sanitizer)
const sanitized = text.replace(/<script[\s\S]*?>[\s\S]*?</script>/gi, '');
// 将清理后的文本插入到光标位置(现代方法)
const start = ta.selectionStart;
const end = ta.selectionEnd;
const newVal = ta.value.slice(0, start) + sanitized + ta.value.slice(end);
ta.value = newVal;
// 将光标移到插入后的位置
const cursor = start + sanitized.length;
ta.setSelectionRange(cursor, cursor);
});
// 使用 navigator.clipboard(主动读取/写入)示例(需 HTTPS 和用户授权)
async function copyTextToClipboard(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text); // 写入剪贴板(异步)
} else {
// 回退:使用 document.execCommand('copy') + 临时 textarea
const tmp = document.createElement('textarea');
tmp.value = text;
document.body.appendChild(tmp);
tmp.select();
try { document.execCommand('copy'); } catch (err) { console.warn('copy fallback failed', err); }
document.body.removeChild(tmp);
}
}
</script>
兼容性说明:
event.clipboardData
在大多数现代桌面浏览器的paste
事件中可用;移动浏览器行为可能差异。navigator.clipboard
(异步 API)需要安全上下文(HTTPS)且浏览器要求交互权限;并非所有浏览器或场景都允许主动读写(读取通常受限)。document.execCommand('copy')
是老方法,仍作为回退,但浏览器对execCommand('paste')
通常限制严格。
潜在问题:
- 剪贴板内容可能包含恶意 HTML/脚本,必须在插入 DOM 前 sanitize(使用 DOMPurify 等库更安全)。
- 读取剪贴板涉及隐私与权限问题,使用时应明确提示用户。
9 自动切换(使用 focus
自动切换用户输入的 input,常见于 OTP)
概念 :用于电话号码/验证码分段输入,用户输入满位后自动 focus()
到下一个输入框;删除时返回上一个。
实践(示例并注释) :
xml
<!-- OTP 示例:4 位数字 -->
<div id="otp">
<input inputmode="numeric" maxlength="1" class="otp" />
<input inputmode="numeric" maxlength="1" class="otp" />
<input inputmode="numeric" maxlength="1" class="otp" />
<input inputmode="numeric" maxlength="1" class="otp" />
</div>
<script>
const inputs = document.querySelectorAll('#otp .otp');
inputs.forEach((el, idx) => {
el.addEventListener('input', (e) => {
// 每次输入后,如果输入了字符,自动跳到下一个
if (e.target.value.length >= 1) {
const next = inputs[idx + 1];
if (next) next.focus();
}
});
el.addEventListener('keydown', (e) => {
// 处理退格键:如果当前为空并按 Backspace,跳回上一个并清空
if (e.key === 'Backspace' && e.target.value === '') {
const prev = inputs[idx - 1];
if (prev) {
prev.focus();
prev.value = '';
e.preventDefault();
}
}
});
});
</script>
潜在问题:
- 输入法(IME)或粘贴多字符时需处理(例如粘贴 "1234" 到第一个输入,应分散填充四个输入)。
10 HTML5 约束验证 API(详细)
概念 :HTML5 提供了约束验证属性(required
, min
, max
, pattern
, type=email
等)和 JS API(checkValidity()
、reportValidity()
、setCustomValidity()
)来检查输入是否符合约束。
10.1 必填字段(required
)
实践:
ini
<input required name="email" type="email" />
form.checkValidity()
在必填为空时返回 false
,reportValidity()
会显示浏览器提示。
10.2 更多输入类型(email
url
)
概念:
type="email"
:浏览器会进行格式检查(含@
等),但不保证是可达地址;可配合pattern
做更严格检查。type="url"
:检查是否符合 URL 格式。
实践:
ini
<input type="email" name="e" />
<input type="url" name="site" />
10.3 数值范围(min
max
step
与 stepUp
/stepDown
)
概念:
input[type="number"]
支持min
、max
、step
。- DOM 方法
stepUp(n)
、stepDown(n)
可调整值(若超出范围,会根据浏览器行为进行限制)。
实践:
typescript
<input id="num" type="number" min="0" max="10" step="1" value="5" />
<button id="up">上一步</button>
<button id="down">下一步</button>
<script>
const num = document.getElementById('num');
document.getElementById('up').addEventListener('click', () => num.stepUp()); // +1
document.getElementById('down').addEventListener('click', () => num.stepDown()); // -1
</script>
10.4 输入模式(pattern
)
概念 :pattern
属性定义一个正则表达式用于匹配控件值(仅在提交/验证时生效)。
实践:
ini
<input pattern="^\d{4}$" title="请输入 4 位数字" />
10.5 检测有效性(checkValidity()
)
实践:
javascript
if (document.getElementById('myForm').checkValidity()) {
// 有效
} else {
// 无效;可用 reportValidity() 显示浏览器提示
document.getElementById('myForm').reportValidity();
}
10.6 禁用验证(novalidate
)
概念 :在 <form novalidate>
上添加 novalidate
将禁用浏览器的约束验证(适用于完全用 JS 自定义验证的场景)。
潜在问题:
- 若禁用验证,开发者必须自己确保所有必要的验证(含服务端验证)都到位,避免安全问题。
11 选择框编程(select
的属性与方法)
概念 :<select>
元素有丰富的 API:options
(集合)、selectedIndex
、multiple
、size
、add()
/remove()
、selectedOptions
、selected
(option 属性)等。
实践(示例并注释) :
xml
<select id="s" size="4">
<option value="a">A</option>
<option value="b">B</option>
</select>
<script>
const s = document.getElementById('s');
// 添加选项:new Option(text, value)
const opt = new Option('C', 'c'); // 创建 Option
s.add(opt); // append to select
// 移除选项:按索引
s.remove(1); // 移除第2项
// 读取选中项
console.log('selectedIndex:', s.selectedIndex);
console.log('selected value:', s.value); // 如果 multiple,value 为第一个被选的值
// multiple 情况,读取所有被选项
if (s.multiple) {
const vals = Array.from(s.selectedOptions).map(o => o.value);
console.log(vals);
}
// insertBefore 用于在指定位置插入(可用来重排)
const opt2 = new Option('D','d');
s.insertBefore(opt2, s.options[0]); // 在最前面插入
</script>
潜在问题:
s.value
在 multiple 的 select 中只返回第一个被选中的值;需要用selectedOptions
获取所有。
12 选项处理(使用 options
属性)
概念 :select.options
是一个 HTMLCollection(类数组),可以用索引读写,或用 length
管理。
实践:
ini
const sel = document.querySelector('select');
for (let i = 0; i < sel.options.length; i++) {
console.log(sel.options[i].value, sel.options[i].text);
}
// 添加
sel.options[sel.options.length] = new Option('text','val');
// 或 sel.add(new Option('t','v'), index);
13 添加选项(通过 new Option
并使用 DOM 句柄或 appendChild
)
实践:
csharp
const opt = new Option('显示文本', 'value');
// 使用 select.add()
select.add(opt, 1); // 在索引 1 位置插入(部分旧浏览器第二个参数要求是引用节点)
14 移除选项(select.remove(index)
与 box.removeChild
)
实践:
csharp
select.remove(2); // 移除索引 2 的选项
// 或:select.removeChild(select.options[2]);
15 移动和重排选项(insertBefore
)
实践(示例:把选中项移动到顶部):
scss
function moveSelectedToTop(sel) {
const opt = sel.options[sel.selectedIndex];
if (opt) {
sel.insertBefore(opt, sel.options[0]); // 将该选项插至首位
}
}
16 富文本的编辑(contenteditable、execCommand、Selection、提交)
概念 :contenteditable
允许元素在浏览器中变成可编辑区域;document.execCommand
(已被弃用但仍广泛支持)曾用于对富文本进行格式化(加粗、插入链接等)。现在推荐使用 Selection
/Range
API 或成熟的富文本编辑器库(Quill、CKEditor、ProseMirror、TipTap 等)。
原理:
contenteditable="true"
将元素标记为可编辑,用户可输入并产生 DOM(<b>
,<i>
,<a>
等)。document.execCommand(command, false, value)
:执行内置命令;命令列表历史上包含bold
、italic
、insertHTML
、createLink
等,但规范标注为"废弃",行为各浏览器略有不同。
对比:
execCommand
:简单、内置但已废弃,不稳定;适合简单 demo。- 使用 Selection + Range 操作 DOM:更现代、可控,但实现复杂。
- 使用第三方库:推荐方式,处理了大量兼容性与功能(工具栏、撤销/重做、粘贴清理、协作等)。
16.1 使用 contenteditable
(示例)
css
<div id="editor" contenteditable="true" style="border:1px solid #ccc; padding:8px; min-height:100px;">
这里是可编辑内容
</div>
16.2 与富文本交互(execCommand
命令列表与示例)
常用命令(非穷尽,且浏览器支持有限) :
bold
,italic
,underline
,strikeThrough
insertHTML
(插入 HTML)createLink
(创建链接,value 为 URL)unlink
insertOrderedList
,insertUnorderedList
justifyLeft
,justifyCenter
,justifyRight
,justifyFull
foreColor
,backColor
(设置颜色)fontName
,fontSize
removeFormat
实践(示例并注释) :
xml
<div>
<button data-cmd="bold">B</button>
<button data-cmd="italic">I</button>
<button data-cmd="createLink">Link</button>
<button id="getHTML">获取 HTML</button>
</div>
<div id="ed" contenteditable="true">可编辑文本</div>
<script>
const ed = document.getElementById('ed');
document.querySelectorAll('button[data-cmd]').forEach(btn => {
btn.addEventListener('click', () => {
const cmd = btn.getAttribute('data-cmd');
if (cmd === 'createLink') {
const url = prompt('输入链接 URL');
if (url) document.execCommand('createLink', false, url); // 插入链接(已废弃 API)
} else {
document.execCommand(cmd, false, null); // 执行命令,例如 bold/italic
}
});
});
document.getElementById('getHTML').addEventListener('click', () => {
// 获取富文本内容(HTML)
console.log(ed.innerHTML);
});
</script>
注意 :execCommand
已被标为过时(deprecated),但多数浏览器仍支持。生产环境建议使用现代编辑库或基于 Selection/Range 的自实现。
16.3 富文本的选择(getSelection()
)
实践:
ini
const sel = window.getSelection();
if (sel.rangeCount > 0) {
const range = sel.getRangeAt(0); // 获取选区
console.log('选区文本:', sel.toString());
// range 可用于插入节点、替换、删除等复杂操作
}
16.4 通过表单提交富文本(获取可编辑区域内容并交给表单提交)
实践(示例并注释) :
xml
<form id="richForm" action="/save" method="POST">
<!-- 隐藏域用于提交编辑器内容 -->
<input type="hidden" name="content" id="hiddenContent" />
<div id="rich" contenteditable="true">初始富文本</div>
<button type="submit">提交</button>
</form>
<script>
const form = document.getElementById('richForm');
const hidden = document.getElementById('hiddenContent');
const rich = document.getElementById('rich');
form.addEventListener('submit', (e) => {
// 在提交前把富文本的 HTML 放入隐藏 input
hidden.value = rich.innerHTML; // 逐行注释:将 HTML 字符串传给后端,后端需 sanitize
// 如果使用 AJAX/fetch,则可直接发送 rich.innerHTML
// 注意:必须在服务器端进行 XSS 过滤和清理
});
</script>
安全性:
- 富文本内容含 HTML,必须在服务器端进行严格消毒(sanitize)以防 XSS。客户端清理(如 DOMPurify)可作为辅助,但不能替代服务端过滤。
拓展/推荐:
- 推荐使用成熟编辑器(Quill、TipTap、CKEditor 5、ProseMirror)来处理复杂功能(撤销/重做、图片上传、插件等)。
潜在问题:
document.execCommand
的未来不确定,不建议构建复杂编辑器在其上长期维护。