{T}

选择框编程

选择框编程

选择框是通过 <select><option> 元素创建的。它常用于在有限选项里挑选值、做条件过滤或配置参数。为了方便与这个控件交互,除了所有表单字段共有的属性和方法外,HTMLSelectElement 类型还提供下列属性和方法。

常见使用场景

  • 让用户从枚举/字典数据中选择(国家、省份、状态、角色等)
  • 构建左右双列表等多选交互,批量移动或排序选项
  • 配合搜索框实现动态过滤或联动选择(省市、类别/子类别)
  • 从服务器动态加载选项,例如根据用户输入即时返回候选项

设计数据结构时,通常会将 value 作为提交给后端的稳定标识,text 为对用户友好的展示文案,两者应保持同步更新。

接口速览

成员类型/返回值说明
add(option, rel)方法在参考项 rel 之前插入新 <option>;传入 null/undefined 时追加到末尾
length数字选项数量,可读写
multiple布尔值是否允许多选,对应 HTML 的 multiple 属性
optionsHTMLOptionsCollection所有 <option> 的集合,可通过索引或名称访问
remove(index)方法移除指定位置的选项
selectedIndex数字第一个被选中的选项下标,无选中项时为 -1
selectedOptionsHTMLCollection只读集合,包含当前所有选中的 <option>(多选时尤其方便)
size数字可见的行数,对应 HTML 的 size 属性
type字符串select-oneselect-multiple
value字符串当前选中项的值,等价于 HTML 中的 value 属性

选择框的 value 属性由当前选中项决定,相应规则如下:

  • 若没有选中项,则返回空字符串
  • 若存在单个选中项,且该 <option> 提供了 value 属性,则直接返回该值(即使值为空字符串)
  • 若选中项未显式提供 value 属性,则返回该选项的文本内容
  • 若选择框允许多选,value 仍然只返回“第一个选中的值”,可通过 selectedOptions 获取全部

以下面的选择框为例:

html
<select name="location" id="selLocation">
  <option value="Sunnyvale, CA">Sunnyvale</option>
  <option value="Los Angeles, CA">Los Angeles</option>
  <option value="Mountain View, CA">Mountain View</option>
  <option value="">China</option>
  <option>Australia</option>
</select>

要一次性取出所有被选中的值,可以利用 selectedOptions

javascript
const selectbox = document.getElementById("selLocation")

const values = Array.from(selectbox.selectedOptions, (option) => option.value)
console.log(values)

在 DOM 中每个 <option> 元素都有个 HTMLOptionElement 对象表示。为便于访问数据,该对象提供如下常用属性:

属性类型说明
index数字当前选项在 options 集合中的索引
label字符串显示在 UI 上的标签文本,等价于 label 属性
selected布尔值是否被选中,设置为 true 会选中该选项
text字符串选项的可见文本
value字符串选项的值,等价于 value 属性
disabled布尔值是否禁用该选项
formHTMLFormElement指向所属的 <form> 元素(若存在)

虽然可以使用常规的 DOM 功能来访问这些信息,但效率较低:

javascript
const selectbox = document.forms[0].elements["location"]

// 不推荐
const text1 = selectbox.options[0].firstChild.nodeValue // 获取第一项的文本
const value1 = selectbox.options[0].getAttribute("value") // 获取第一项的值

// 推荐
const text2 = selectbox.options[0].text // 选项文本
const value2 = selectbox.options[0].value // 选项值

事件与交互

change 与 input

javascript
const selectEl = document.getElementById("selLocation")

selectEl.addEventListener("change", (event) => {
  console.log("选项变更", event.target.value)
})

selectEl.addEventListener("input", (event) => {
  // 某些浏览器会同步触发 input,必要时做防抖/节流
  console.log("输入事件", event.target.value)
})
  • change 在选择项确定后立即触发,不需要等待 blur
  • 需要实时联动时监听 change 更稳妥;input 可作为补充
  • 若控件被禁用后重新启用,可根据业务需要手动触发一次更新事件

focus 与 blur

选择框同样会触发 focusblur,可用于给出操作提示或执行校验:

javascript
const helper = document.getElementById("selectHelp")

selectEl.addEventListener("focus", () => {
  helper.textContent = "使用上下键或输入首字母可以快速选中"
})

selectEl.addEventListener("blur", () => {
  helper.textContent = ""
})

搭配 aria-describedby 可以让读屏软件感知提示内容。

联动选择器示例

javascript
const countrySelect = document.getElementById("country")
const citySelect = document.getElementById("city")

const cityMap = {
  China: ["Beijing", "Shanghai", "Shenzhen"],
  USA: ["Sunnyvale", "Los Angeles", "Austin"]
}

countrySelect.addEventListener("change", () => {
  const cities = cityMap[countrySelect.value] ?? []
  citySelect.options.length = 0

  for (const city of cities) {
    citySelect.add(new Option(city, city))
  }

  citySelect.disabled = cities.length === 0
})

根据需要可以在末尾设置 citySelect.selectedIndex = 0 并手动触发 change,确保联动后的默认值同步。

选择选项

selectedIndex

对于单选框,访问选中项的最简单方式,就是使用选择框的 selectedIndex 属性:

javascript
const selectEl = document.getElementById("selLocation")

const selectedIndex = selectEl.selectedIndex
if (selectedIndex === -1) {
  console.log("Nothing selected")
} else {
  const selectedOption = selectEl.options[selectedIndex]
  console.log(`Selected index: ${selectedIndex}`)
  console.log(`Selected text: ${selectedOption.text}`)
  console.log(`Selected value: ${selectedOption.value}`)
}

对于多选框,获取 selectedIndex 属性返回选中的第一项的索引。但设置 selectedIndex 会移除所有选项,只选择指定的项。

selected

通过取得选项的引用并将其 selected 属性设置为 true 来设置选中:

javascript
selectbox.options[0].selected = true

⚠️ selectedIndex 不同,在多选框中设置选项的 selected 属性,不会取消对其他选中项的选择,因而可以动态选中任意多个项。但在单选选择框中,修改某个选项的 selected 属性则会取消对其他选项的选择。 注意:将 selected 属性设置为 false 对单选选择框没有影响 实际上 selected 属性的作用主要是,确定用户选择了哪一项。要取得所有选中的项,可以循环遍历选项集合并判断 selected 属性:

javascript
function getSelectedOptions(selectEl) {
  const result = []
  for (const option of selectEl.options) {
    if (option.selected) {
      result.push(option)
    }
  }
  return result
}

const selectEl = document.getElementById("selLocation")
const selectedOptions = getSelectedOptions(selectEl)
let message = ""

for (const option of selectedOptions) {
  message += `Selected index: ${option.index}\n`
  message += `Selected text: ${option.text}\n`
  message += `Selected value: ${option.value}\n`
}
console.log(message)

// 若只需值数组,可直接映射
const values = selectedOptions.map((option) => option.value)

添加选项

可以使用 JavaScript 动态创建选项,并将它们添加到选择框:

  1. 通过 DOM API 创建节点
  2. 使用 Option 构造函数
  3. 调用选择框的 add 方法
javascript
const selectbox = document.getElementById("selLocation")

// 1. DOM 创建
const option1 = document.createElement("option")
option1.value = "Option value"
option1.textContent = "Option text"
selectbox.append(option1)

// 2. 构造函数
const option2 = new Option("Option text", "Option value")
selectbox.append(option2)

// 3. add 方法(第二个参数为参考节点,可传 null/undefined 追加到末尾)
const option3 = new Option("Inserted before first", "before")
selectbox.add(option3, selectbox.options[0] ?? null)
  • append/appendChild 适合批量插入,可先构建 DocumentFragment 后统一追加
  • Option 构造函数语法简洁,但不便携带自定义属性
  • add 支持指定插入位置,API 与旧规范兼容

如果想将新选项插入列表中间,可以配合 insertBefore 或给 add 传入参考项。

移除选项

移除选项的方式有很多种:

  • 使用 removeChild:传入要移除的选项节点
  • 使用选择框的 remove(index) 方法
  • 直接设置 selectbox.options.length = 0selectbox.innerHTML = ""(注意:后者会重建 DOM,可能丢失事件监听)
javascript
const selectbox = document.getElementById("selLocation")

// 移除第一个选项
selectbox.removeChild(selectbox.options[0])
selectbox.remove(0)

// 清空所有选项
while (selectbox.options.length > 0) {
  selectbox.remove(0)
}

// 或者更简洁:
selectbox.options.length = 0

移动和重排选项

使用 appendChild 可以将一个选择框中的选项直接移动到另一个选择框:

javascript
const selectbox1 = document.getElementById("selLocations1")
const selectbox2 = document.getElementById("selLocations2")

// 将所有选中项移动到目标选择框
for (const option of Array.from(selectbox1.selectedOptions)) {
  selectbox2.appendChild(option)
}

重排选项次序最好的方式是使用 insertBeforeadd 的第二个参数:

javascript
const selectbox = document.getElementById("selLocation")

// 将索引为 2 的选项移动到索引为 0 的位置
const optionToMove = selectbox.options[2]
selectbox.insertBefore(optionToMove, selectbox.options[0])

// 使用 add API 达到相同效果
selectbox.add(optionToMove, selectbox.options[0])

表单集成与校验

  • 使用 new FormData(form) 可以一次性读取表单中所有选择框的值,多选字段会返回多条记录:

    javascript
    const form = document.querySelector("form")
    
    form.addEventListener("submit", (event) => {
      event.preventDefault()
      const data = new FormData(form)
      console.log(data.get("location"))
      console.log(data.getAll("hobbies")) // 针对多选
    })
  • 选择框同样支持原生的约束校验(requiredmultiplesize 等)。可以通过 selectbox.setCustomValidity() 自定义错误消息

  • 添加 <label for="selectId"> 可以显著提升无障碍体验,多选时可额外提供帮助文本说明如何选择多项(例如提示使用 Ctrl/Cmd

批量更新与性能

在批量更新大量选项时,避免逐项插入造成的反复重排或闪烁:

javascript
function populateSelect(selectEl, items) {
  const fragment = document.createDocumentFragment()

  for (const item of items) {
    const option = new Option(item.label, item.value)
    fragment.append(option)
  }

  selectEl.replaceChildren(fragment)
}
  • DocumentFragment 可显著降低插入成本;旧浏览器可退化为 appendChild
  • replaceChildren 会一次性替换内容,必要时事先缓存旧选中值再恢复
  • 需要清空时比 innerHTML = "" 更高效且不会执行字符串解析

加载远程数据的常见模式:

javascript
async function fetchOptions(url, selectEl, { placeholder } = {}) {
  selectEl.disabled = true
  selectEl.options.length = 0

  if (placeholder) {
    selectEl.add(new Option(placeholder, ""))
  }

  try {
    const response = await fetch(url)
    const data = await response.json()

    for (const { label, value } of data) {
      selectEl.add(new Option(label, value))
    }
  } catch (error) {
    console.error("加载选项失败", error)
    selectEl.setCustomValidity("暂时无法加载选项,请稍后再试")
  } finally {
    selectEl.disabled = false
  }
}

加载过程中可设置 aria-busy="true" 或展示骨架屏,避免用户误以为控件不可用。

可访问性与用户体验

  • <select> 提供语义化的 <label>,必要时通过 aria-describedby 关联提示文本
  • 多选框应明确告知“按住 Ctrl/Cmd 多选”等操作方式
  • 移动端通常渲染为原生滚轮式 UI,避免过度自定义导致可点击面积不足
  • 长列表建议提供搜索、分组或首字母跳转功能,降低滚动成本
  • 自定义样式时若隐藏原生选择框,需要额外实现键盘操作和焦点管理

常见陷阱

  • 遗漏 value 属性会导致提交到后端的是文本内容,后续改文案可能破坏数据
  • 在多选框上读取 selectEl.value 只返回第一个值,应遍历 selectedOptions
  • 动态插入 <option> 后立即读取 selectedIndex,需确保浏览器完成渲染(可延迟到 requestAnimationFrame
  • 删除或禁用选项后忘记重置表单校验状态,可能保留旧的错误提示
  • 在 React/Vue 等框架中同时操作 DOM 与受控组件 value,会让 UI 与状态不同步