queryselectorall 返回静态 nodelist,是调用时 dom 状态的只读快照;后续 dom 变更不影响其内容、长度或顺序,新增或删除 li 元素均不改变已获取的 nodelist。

querySelector 返回的是静态节点,本质是“快照”——它在调用那一刻就把匹配到的 DOM 元素**一次性抓取并固化成 NodeList**,后续无论你增删改查 DOM,这个 NodeList 里的内容、长度、顺序都不再变化。
静态节点:一次查询,永久定格
比如执行 const items = document.querySelectorAll('li'),浏览器会立刻遍历当前 DOM 树,把所有 <li> 元素(按文档顺序)装进一个只读的 NodeList 数组。这个数组不和 DOM 绑定,只是当时状态的副本。
- 新增
<li>?不影响items.length - 删除某个
<li>?items[0]还是指向原来的元素(除非那个元素被移出文档,此时引用仍存在但已脱离 DOM) - 循环遍历时用
for (let i = 0; i 是安全的,不会死循环
动态节点:每次访问都重新查 DOM
像 document.getElementsByTagName('li') 返回的是 HTMLCollection,它本身不存元素,只存查询规则(如“找所有标签名为 li 的子元素”)。每次你读 .length、取 [0] 或调用 .item(0),浏览器都会实时重跑一次匹配逻辑。
- DOM 新增一个
<li>,下一次读list.length就自动 +1 - 在 for 循环里边遍历边往父容器加
<li>,很可能触发无限循环 - 它更轻量(创建快),但反复访问代价高(每次都查树)
关键不在“存不存元素”,而在“何时查、查几次”
两者存的都是 DOM 节点的引用,不是拷贝。区别在于:
- 静态 NodeList:调用时查一次,结果缓存;之后所有操作都基于这份缓存
- HTMLCollection:调用时不查,访问属性或索引时才查;每次都是新鲜结果
为什么 querySelectorAll 必须静态?规范强制
W3C 明确规定:querySelectorAll() 必须返回静态 NodeList。这是为了行为可预测——开发者能放心地遍历、映射、传参,不必担心中途 DOM 变动导致逻辑错乱。而 getElementsByXXX 系列没这限制,历史原因保留了动态语义。










