02元素查询

JS 操控 DOM 的第一步是「找到元素」——就像在文件柜里翻档案,先得知道档案放在哪一格。 浏览器给了 4 个核心查询 API:getElementById / getElementsByClassName / querySelector / querySelectorAll。 其中query 系列最万能——它接收完整的 CSS 选择器,几乎能查到你想要的任何元素。

约 18 分钟 6 个动手实验 DOM 查询四件套

一、四件套:id / class / 选择器 / all

DOM 查询 API 有两大家族: getElement 系(最老、性能略好)和 querySelector 系(最灵活)。 它们最大的差异在返回值:一个返回单个 Element, 一个返回「集合」(NodeList 或 HTMLCollection)。

getElementById('id')
返回 Element 或 null,单个匹配
getElementsByClassName('x')
返回 实时 HTMLCollection,多个匹配
querySelector('sel')
接收完整 CSS 选择器,返回第一个匹配
querySelectorAll('sel')
接收完整 CSS 选择器,返回静态 NodeList
四 API 对比:语法 / 返回值 / 是否实时更新 / 适用场景
API语法返回值实时更新适用场景
getElementById ('#' 可省) Element | null — 已知唯一 id 时最快
getElementsByClassName ('.', 多类空格分) HTMLCollection(实时) ✓ 是 要随 DOM 变化自动同步集合
getElementsByTagName ('div') HTMLCollection(实时) ✓ 是 同标签批量操作
querySelector (完整 CSS) Element | null — 复杂选择器、要第一个
querySelectorAll (完整 CSS) NodeList(静态) ✗ 否 复杂选择器、批量操作首选
一句话决策:默认用 querySelector / querySelectorAll—— 因为它接收完整 CSS 选择器,能干几乎所有事;只有要「实时同步集合」时才回头用 getElementsByClassName。

二、querySelector 与 CSS 选择器

querySelector(selector) 接收任意合法的 CSS 选择器, 返回第一个匹配的 Element,没找到则返回 null。 写法跟 CSS 完全一样:类 .x、id #y、属性 [data-foo]、 伪类 p:nth-child(2)、后代 .a .b、群组 .a, .b。

点按钮看每种选择器命中什么:① 类选择器 ② id 选择器 ③ 属性选择器 ④ 伪类 nth-child —— 看输出框里第一行的「命中文本」
没找到返回 null,不是空对象。 对返回值操作前必须先判断 if (el) { ... },否则下一行就抛 Cannot read property '...' of null——这是新人最常踩的报错之一。

三、querySelectorAll 与 NodeList

想一次性拿所有匹配的元素?用 querySelectorAll(selector), 它返回一个 NodeList(节点列表)—— 像一个「只读的数组」,静态:DOM 之后增删元素, 这个 NodeList 不会自动同步。

NodeList 关键特性速查:哪些能用 / 哪些不能用
能力NodeList(querySelectorAll)
length✓ 元素个数
[i] 下标访问✓ 第 i 个元素
forEach((el) => ...)✓ 直接遍历
entries / keys / values✓ 迭代器三件套
for...of✓ 可迭代
map / filter / reduce✗ 没有!需 Array.from() 转换
DOM 增删后自动同步?✗ 静态,不跟着变
namedItem('id')✗ 没有(HTMLCollection 才支持)
点按钮遍历 querySelectorAll('p'):① forEach 逐个打印 textContent + tagName + classList;② 验证 map 不存在,会报错 —— 改 CSS 选择器试试 div / .tag
NodeList 是「够用但不全」: 需要 map / filter / reduce 时, 一律 Array.from(nodeList) 转成真数组再调——见第五节。

四、getElement 系列与 HTMLCollection

老牌 getElementById / getElementsByClassName / getElementsByTagName 返回的是 HTMLCollection——和 NodeList 长得像但关键差异在实时性: DOM 里增删元素后,HTMLCollection 会自动同步,length 立刻跟着变。 NodeList(querySelectorAll)则不会。

HTMLCollection vs NodeList:本质差异
特性HTMLCollectionNodeList
来源 getElement 系 / children querySelectorAll / childNodes
实时性 ✓ 实时同步 ✗ 静态快照
只含元素? ✓ 只含 Element ✗ 可能含文本 / 注释节点
forEach ✗ 没有 ✓ 自带
namedItem('id') ✓ 可用 id 直接取 ✗ 没有
map / filter ✗ 没有 ✗ 没有
点按钮看「实时性」:① getElementById 拿 #target;② getElementsByTagName('li') 拿到实时 HTMLCollection;③ 再「加一个 li」——注意集合 length 自动从 3 变 4
「实时」是双刃剑: 拿到 HTMLCollection 后,循环里又改 DOM 可能让下标错位。 推荐先把集合转成静态数组再遍历—— Array.from(liveLis).forEach(...)。

五、遍历 NodeList vs HTMLCollection vs 数组

拿到集合后想遍历,三者能力不同: NodeList 有 forEach,HTMLCollection 没有; 两者都没有 map / filter / reduce—— 想用这些函数式方法,必须 Array.from() 一步转真数组。

点按钮看三类集合的遍历差异:① NodeList 直接 forEach;② HTMLCollection 报错(无 forEach);③ Array.from + .map 一行拿到「文本数组」——改 CSS 选择器试试
展开运算符 [...nodeList] 是最简洁的转换写法, 等价于 Array.from(nodeList)——但前者不能传映射函数(如 Array.from(els, el => el.id))。两种都用,看场景挑。

六、查询祖先与兄弟

查到的不止有「自己」,还有「上下左右」—— element.parentElement 直接父、 previousElementSibling / nextElementSibling 左右兄弟、 element.closest(selector) 找最近的祖先(含自身)。 其中 closest 最常用:一层层往上找,第一个匹配的就停。

亲戚查询 5 个属性:方向 / 返回值 / 一句话
属性方向返回找不到
parentElement 上一级(父) Element | null 到顶返回 null
previousElementSibling 上一个兄弟 Element | null 没有返回 null
nextElementSibling 下一个兄弟 Element | null 没有返回 null
closest(sel) 向上找(含自身) Element | null 找不到返回 null
children 直接子元素 HTMLCollection(实时) —
点按钮从「最内层 div」往上找:① closest('.x') 一路向上命中第 2 层(最近祖先);② parentElement 拿到直接父;③ previousElementSibling 拿到上一个兄弟 —— 看每次输出框的「命中层级」
closest 是事件委托的基石: 列表里点了某行,想知道它属于哪个 <ul>,直接 e.target.closest('ul') 一行搞定——比维护「父亲是谁」的变量省心多了。

七、查询实战:批量改样式

把 4 个 API 串起来用,最常见的需求是 「批量选中所有同类元素,一起改样式」—— 一行 querySelectorAll('.card').forEach(...) 就能搞定。 本节用 6 张卡片(不同 data-color)做实战演示。

点按钮看批量查询效果:① 给所有 .card 加 active 类;② 只挑 data-color="amber" 的染色;③ 清空所有 active —— 改 CSS 选择器试试 .card.active
链式批量操作: document.querySelectorAll('.item').forEach(el => el.classList.add('on')) ——选中、加类、一步到位。后面学「事件」时,会用这招给每个按钮统一绑事件。

八、本章小结

  • 四 API:getElementById(Element)/ getElementsByClassName(实时 HTMLCollection)/ querySelector(第一个 Element)/ querySelectorAll(静态 NodeList)。
  • querySelector / All 接收完整 CSS 选择器(类/id/属性/伪类/后代/群组),最万能;找不到返回 null。
  • NodeList 静态,HTMLCollection 实时——HTMLCollection 增删元素后 length 自动同步。
  • NodeList 有 forEach;HTMLCollection 没有;两者都没 map / filter, 需要时 Array.from(...) 转成真数组。
  • 亲戚查询:parentElement / previousElementSibling / nextElementSibling;closest() 找最近祖先,是事件委托的基石。
  • 批量改样式:querySelectorAll('.x').forEach(el => el.classList.add('y')) 一行搞定。
动手练习: ① 任务一:选一个列表页 HTML,用 querySelectorAll('.item') 给所有项加 active 类, 再用 Array.from() 转成数组,用 .map 拿出每项的 textContent; ② 任务二:点击某个 <li> 时,用 e.target.closest('ul') 找到它所属的列表, 把这个 <ul> 的 id 打到 __out(事件下节课学,这里先记住 API); ③ 任务三:写一段验证:拿 getElementsByClassName('x') 集合,循环里 appendChild 一个新元素, 看循环里 length 是否被「实时同步」影响——体会为什么「先转数组再遍历」更稳。