02元素查询
JS 操控 DOM 的第一步是「找到元素」——就像在文件柜里翻档案,先得知道档案放在哪一格。
浏览器给了 4 个核心查询 API:getElementById /
getElementsByClassName / querySelector /
querySelectorAll。
其中query 系列最万能——它接收完整的 CSS 选择器,几乎能查到你想要的任何元素。
一、四件套:id / class / 选择器 / all
DOM 查询 API 有两大家族:
getElement 系(最老、性能略好)和 querySelector 系(最灵活)。
它们最大的差异在返回值:一个返回单个 Element,
一个返回「集合」(NodeList 或 HTMLCollection)。
| API | 语法 | 返回值 | 实时更新 | 适用场景 |
|---|---|---|---|---|
| getElementById | ('#' 可省) |
Element | null | — | 已知唯一 id 时最快 |
| getElementsByClassName | ('.', 多类空格分) |
HTMLCollection(实时) | ✓ 是 | 要随 DOM 变化自动同步集合 |
| getElementsByTagName | ('div') |
HTMLCollection(实时) | ✓ 是 | 同标签批量操作 |
| querySelector | (完整 CSS) |
Element | null | — | 复杂选择器、要第一个 |
| querySelectorAll | (完整 CSS) |
NodeList(静态) | ✗ 否 | 复杂选择器、批量操作首选 |
二、querySelector 与 CSS 选择器
querySelector(selector) 接收任意合法的 CSS 选择器,
返回第一个匹配的 Element,没找到则返回 null。
写法跟 CSS 完全一样:类 .x、id #y、属性 [data-foo]、
伪类 p:nth-child(2)、后代 .a .b、群组 .a, .b。
第 1 个 p
第 2 个 p(class="x" id="y" data-foo="bar")
第 3 个 p(class="x" data-foo="baz")
第 4 个 p
span(class="x",不是 p)null,不是空对象。
对返回值操作前必须先判断 if (el) { ... },否则下一行就抛
Cannot read property '...' of null——这是新人最常踩的报错之一。三、querySelectorAll 与 NodeList
想一次性拿所有匹配的元素?用 querySelectorAll(selector),
它返回一个 NodeList(节点列表)——
像一个「只读的数组」,静态:DOM 之后增删元素,
这个 NodeList 不会自动同步。
| 能力 | NodeList(querySelectorAll) |
|---|---|
length | ✓ 元素个数 |
[i] 下标访问 | ✓ 第 i 个元素 |
forEach((el) => ...) | ✓ 直接遍历 |
entries / keys / values | ✓ 迭代器三件套 |
for...of | ✓ 可迭代 |
map / filter / reduce | ✗ 没有!需 Array.from() 转换 |
| DOM 增删后自动同步? | ✗ 静态,不跟着变 |
| namedItem('id') | ✗ 没有(HTMLCollection 才支持) |
第一段文字
第二段 class="x"
第三段 class="x tag"
第四段没类
map / filter / reduce 时,
一律 Array.from(nodeList) 转成真数组再调——见第五节。四、getElement 系列与 HTMLCollection
老牌 getElementById / getElementsByClassName / getElementsByTagName
返回的是 HTMLCollection——和 NodeList 长得像但关键差异在实时性:
DOM 里增删元素后,HTMLCollection 会自动同步,length 立刻跟着变。
NodeList(querySelectorAll)则不会。
| 特性 | HTMLCollection | NodeList |
|---|---|---|
| 来源 | getElement 系 / children | querySelectorAll / childNodes |
| 实时性 | ✓ 实时同步 | ✗ 静态快照 |
| 只含元素? | ✓ 只含 Element | ✗ 可能含文本 / 注释节点 |
| forEach | ✗ 没有 | ✓ 自带 |
| namedItem('id') | ✓ 可用 id 直接取 | ✗ 没有 |
| map / filter | ✗ 没有 | ✗ 没有 |
- 第 1 项(id="target")
- 第 2 项
- 第 3 项
Array.from(liveLis).forEach(...)。五、遍历 NodeList vs HTMLCollection vs 数组
拿到集合后想遍历,三者能力不同:
NodeList 有 forEach,HTMLCollection 没有;
两者都没有 map / filter / reduce——
想用这些函数式方法,必须 Array.from() 一步转真数组。
- 苹果
- 香蕉
- 橘子
- 葡萄
[...nodeList] 是最简洁的转换写法,
等价于 Array.from(nodeList)——但前者不能传映射函数(如
Array.from(els, el => el.id))。两种都用,看场景挑。六、查询祖先与兄弟
查到的不止有「自己」,还有「上下左右」——
element.parentElement 直接父、
previousElementSibling / nextElementSibling 左右兄弟、
element.closest(selector) 找最近的祖先(含自身)。
其中 closest 最常用:一层层往上找,第一个匹配的就停。
| 属性 | 方向 | 返回 | 找不到 |
|---|---|---|---|
| parentElement | 上一级(父) | Element | null | 到顶返回 null |
| previousElementSibling | 上一个兄弟 | Element | null | 没有返回 null |
| nextElementSibling | 下一个兄弟 | Element | null | 没有返回 null |
| closest(sel) | 向上找(含自身) | Element | null | 找不到返回 null |
| children | 直接子元素 | HTMLCollection(实时) | — |
closest 是事件委托的基石:
列表里点了某行,想知道它属于哪个 <ul>,直接
e.target.closest('ul') 一行搞定——比维护「父亲是谁」的变量省心多了。七、查询实战:批量改样式
把 4 个 API 串起来用,最常见的需求是
「批量选中所有同类元素,一起改样式」——
一行 querySelectorAll('.card').forEach(...) 就能搞定。
本节用 6 张卡片(不同 data-color)做实战演示。
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'))一行搞定。
querySelectorAll('.item') 给所有项加 active 类,
再用 Array.from() 转成数组,用 .map 拿出每项的 textContent;
② 任务二:点击某个 <li> 时,用 e.target.closest('ul') 找到它所属的列表,
把这个 <ul> 的 id 打到 __out(事件下节课学,这里先记住 API);
③ 任务三:写一段验证:拿 getElementsByClassName('x') 集合,循环里 appendChild 一个新元素,
看循环里 length 是否被「实时同步」影响——体会为什么「先转数组再遍历」更稳。