下拉与建议:select / optgroup / option 与 datalist
从一组固定选项里取值,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> 元素。