← HTML 表单:从控件到提交与校验 / 下拉与建议:select / optgroup / option 与 datalist 待审核 10 / 24
select · optgroup · option · datalist

下拉与建议:select / optgroup / optiondatalist

从一组固定选项里取值,HTML 给了两条路。一条是 <select>:它限定取值范围——用户只能选你给的 <option>,可单选(下拉)也可 multiple(列表框),还能用 <optgroup> 分组。另一条是 <datalist>:它给一个 <input> 挂上自动补全建议,候选只是提示,用户照样能输列表外的值。两者最容易混的就是这点——一个是约束,一个是建议;以及多选 <select> 的「当前值」不能简单读 .value。本页用原生控件把这些读值规则当场点出来。

1 · select:单选下拉 vs 多选列表框,以及怎么读出选中值

左边是单选 <select>(默认下拉),含一个 <optgroup> 分组、一个 disabled 不可选项、一个 selected 默认项;右边是 <select multiple size="5">——加了 multiple 就渲染成列表框,size 控制可见行数。两边都改改,看下方读出来的差异:单选的 .value 直接给出选中项的 value;多选时 .value 只取第一个选中 option 的 value,要拿到全部选中,得读 .selectedOptions

required + 空 value 占位项强制选择。 单选 <select> 上的 required 想生效,必须有一个 value 为空字符串的 option(通常是首项的占位「请选择」)且它当前被选中——此时表单不通过校验。把占位项的 value 设成非空就拦不住了。多选的 required 则要求至少选中一项。校验细节见 约束校验

2 · datalist:它只是建议,不是约束

<datalist> 装一组 <option> 当候选,被 <input list="dlId">list 属性引用。和 <select> 的根本区别:它只提供自动补全候选,不限制输入——下面这个框完全可以输列表里没有的值(试试输入「榴莲」),照样被接受、照样进 .value。所以它适合「常见值给提示、又允许自由输入」的场景,而非用来限定取值。

同一个 <datalist> 能服务多种 <input> 除了 text,它还能给 type="range" 提供刻度吸附点(如上)、给 type="color" 提供预设色板、给 date / time 提供常用日期。对 range / color 这类控件,datalist 里 option 的 value 才是被读取的数据,option 的可见文本通常留空。

3 · option 的 value 默认规则与 label 覆盖

<option> 的提交值取自 value 属性;默认 value 时,值等于 option 的文本内容。另有一个 label 属性可覆盖显示文本(显示 label、提交仍是 value)。下面三个 option 演示这两条规则,读出各自的 .value / .text / .label

把三者的读值规则汇总成一张表:

读法 / 规则 单选 <select> 多选 <select multiple> <datalist> + input
是否约束取值 ,只能选给定 option 是,只能选给定 option ,仅建议,可输列表外值
怎么读当前值 sel.value(= 选中项 value) [...sel.selectedOptions].map(o=>o.value) input.value(就是输入框文本)
.value 的含义 选中项的 value 只取第一个选中项 value(不够用) 输入框当前文本(与 datalist 无关)
辅助 IDL .selectedIndex / .options / .length .selectedOptions / .selectedIndex(= 第一个) datalist.options(候选集合)
触发的事件 选定后 change 每次增减选中 change input / change(同普通文本框)
option 无 value value 回退为 option 的文本内容(textContent trim 后) 同左 同左
option 有 label 显示 label,提交 / 读出仍是 value(label 不进值) 同左 同左

4 · 读值的三种正确写法

<!-- 单选: .value 就是选中项的 value -->
<select id="city">
  <optgroup label="华东">
    <option value="sh" selected>上海</option>
    <option value="nj" disabled>南京</option>
  </optgroup>
</select>
city.value                                  // "sh"

<!-- 多选: .value 只给第一个,要遍历 selectedOptions -->
<select id="tags" multiple size="5">…</select>
tags.value                                  // 只是第一个选中项的 value
[...tags.selectedOptions].map(o => o.value) // ["rs", "py"] —— 全部选中

<!-- datalist: 只是建议,读的是 input 自己的文本 -->
<input id="fruit" list="fruits" />
<datalist id="fruits">
  <option value="苹果"></option>
</datalist>
fruit.value                                 // 可能是 "苹果",也可能是任意列表外文本

<!-- option: 无 value 回退文本; label 覆盖显示 -->
<option>樱桃</option>             // value === "樱桃"
<option value="c" label="显示文本">真实文本</option>  // 显示 label,值为 "c"

多选取值不要读 .value 多选 <select>.value 只返回第一个选中 option 的 value,.selectedIndex 也只是第一个选中项的下标——读全部选中项必须遍历 .selectedOptions(一个 HTMLCollection)或 .options 里筛 .selected。用 new FormData(form) 时,多选会为每个选中项各产出一条同名 entry。

<datalist> 不做任何校验约束。 它和约束校验是两件事——输入框接受列表外的值不算非法,:invalid 不会因此触发。想「必须从候选里选」,要么改用 <select>,要么自行用 pattern / 脚本校验。另外 <optgroup> 不能嵌套,且 label 属性必填、渲染为一行不可选的组标题;给 <optgroup>disabled 会禁用整组 option。要让 <select> 与其他控件统一收编进同一张表单,见 <form> 元素