前端必修:从表单基础到富文本编辑,一文吃透 HTML 表单编程与交互

1 表单基础(acceptCharset / action / elements / enctype / length / method / name / reset / submit / target 等)

概念 :这些是 <form> 的属性或与表单相关的 DOM 属性/方法,用于定义提交目标、编码方式、提交方法以及通过脚本访问表单控件等。

原理

  • action:提交目标 URL(表单数据要发送到哪里)。
  • method:提交方法(GETPOST)。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.enctypeFormData:在使用 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,按索引、按 nameid 访问。也可使用 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 可用于 fetchfetch('/api', { method: 'POST', body: new FormData(f) })
  • Object.fromEntries(new FormData(f)) 可快速构造纯 JS 对象(注意多值同名字段会只保留最后一个值,或需特殊处理)。

潜在问题

  • 使用 form.elements[name] 时如果 name 与 window 全局变量冲突会出问题;尽量使用 id + querySelectorFormData 来避免歧义。

5 表单验证(使用 form.checkValidity()

概念 :HTML5 约束验证 API(Constraint Validation API)允许以声明式(requiredpatterntype="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: 控件类型与当前值 -->

拓展

  • disabledreadOnly 的区别: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"> 是最常见的单行文本输入控件,支持 maxlengthplaceholderpatternautocomplete 等属性。

实践

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/EndsetSelectionRange,最佳实践是用 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() 在必填为空时返回 falsereportValidity() 会显示浏览器提示。


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 stepstepUp/stepDown

概念

  • input[type="number"] 支持 minmaxstep
  • 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(集合)、selectedIndexmultiplesizeadd()/remove()selectedOptionsselected(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):执行内置命令;命令列表历史上包含 bolditalicinsertHTMLcreateLink 等,但规范标注为"废弃",行为各浏览器略有不同。

对比

  • 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 的未来不确定,不建议构建复杂编辑器在其上长期维护。
相关推荐
Dragon Wu几秒前
前端 下载后端返回的二进制excel数据
前端·javascript·html5
北海几经夏6 分钟前
React响应式链路
前端·react.js
晴空雨35 分钟前
React Media 深度解析:从使用到 window.matchMedia API 详解
前端·react.js
一个有故事的男同学35 分钟前
React性能优化全景图:从问题发现到解决方案
前端
探码科技37 分钟前
2025年20+超实用技术文档工具清单推荐
前端
Juchecar40 分钟前
Vue 3 推荐选择组合式 API 风格(附录与选项式的代码对比)
前端·vue.js
uncleTom66643 分钟前
# 从零实现一个Vue 3通用建议选择器组件:设计思路与最佳实践
前端·vue.js
影i43 分钟前
iOS WebView 异步跳转解决方案
前端
Nicholas6844 分钟前
flutter滚动视图之ScrollController源码解析(三)
前端
爪洼守门员44 分钟前
安装electron报错的解决方法
前端·javascript·electron