跟着 MDN 学 HTML day_46:深入理解 HTMLCollection 与 NodeList
📑 目录
| 左列章节 | 右列章节 |
|---|---|
| [一、HTMLCollection 接口概述](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) | [二、HTMLCollection.length 属性](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) |
| [三、HTMLCollection.item() 方法](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) | [四、HTMLCollection.namedItem() 方法](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) |
| [五、HTMLCollection 的即时更新特性](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) | [六、HTMLCollection 属性的特殊访问方式](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) |
| [七、NodeList 与 HTMLCollection 的区别](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) | [八、NodeList.item() 方法](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) |
| [九、遍历方式对比](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) | [十、实际应用场景与最佳实践](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) |
| [十一、总结与实践要点](#左列章节 右列章节 一、HTMLCollection 接口概述 二、HTMLCollection.length 属性 三、HTMLCollection.item() 方法 四、HTMLCollection.namedItem() 方法 五、HTMLCollection 的即时更新特性 六、HTMLCollection 属性的特殊访问方式 七、NodeList 与 HTMLCollection 的区别 八、NodeList.item() 方法 九、遍历方式对比 十、实际应用场景与最佳实践 十一、总结与实践要点) |
一、HTMLCollection 接口概述
HTMLCollection 接口表示一个包含了元素的通用集合,这些元素按照文档流中的顺序排列。它是一个与 arguments 相似的类数组对象,提供了用来从集合中选择元素的方法和属性。
由于历史原因,DOM4 之前实现该接口的集合只能包含 HTML 元素,因此这个接口被命名为 HTMLCollection。实际上,HTMLCollection 在 HTML DOM 中具有一个非常重要的特性:它是即时更新的。当其所包含的文档结构发生改变时,这个集合会自动更新,反映最新的 DOM 状态。
代码示例:获取并验证 HTMLCollection
javascript
// 获取页面中的所有 div 元素
const divs = document.getElementsByTagName("div");
console.log(divs instanceof HTMLCollection); // true
console.log(divs.length); // 当前 div 元素的数量
核心结论 :正是由于
HTMLCollection是即时更新的,在对集合进行迭代的同时如果修改了 DOM 结构,可能会导致意想不到的结果。因此安全做法是在迭代前创建集合的副本。
代码示例:安全迭代 ------ 先创建副本
javascript
// 安全的做法:先创建副本再迭代
const divArray = Array.from(document.getElementsByTagName("div"));
divArray.forEach(div => {
div.parentNode.removeChild(div); // 安全删除,因为遍历的是副本
});
⚠️ 【重点 / 面试考点】
HTMLCollection是**即时更新(live)**的类数组对象,DOM 变化会自动反映到集合中getElementsByTagName、getElementsByClassName、document.forms、document.images等都返回HTMLCollection- 遍历时修改 DOM 会导致跳过元素或死循环,迭代前务必创建副本
Array.from()或展开运算符[...collection]均可将HTMLCollection转为静态数组
二、HTMLCollection.length 属性
length 属性返回 HTMLCollection 中元素的数量,它是一个只读的正整数。这一属性在 DOM 编程中非常常用,既可以用来判断集合是否为空,也可以作为循环的边界条件。
代码示例:length 属性的基本用法
javascript
// 获取所有具有 .test 类的元素
const items = document.getElementsByClassName("test");
console.log("集合中元素数量:", items.length);
// 遍历集合并拼接 innerHTML
let gross = "";
for (let i = 0; i < items.length; i++) {
gross += items[i].innerHTML;
}
console.log(gross); // 输出所有 .test 元素的 HTML 内容
length 属性在判断集合是否真实存在元素时也十分有用。与 NodeList 不同,HTMLCollection 没有 forEach 方法 ,因此传统 for 循环仍然是遍历它的主要方式。
代码示例:判断集合是否为空
javascript
if (items.length > 0) {
console.log("存在匹配的元素");
} else {
console.log("没有找到任何匹配的元素");
}
三、HTMLCollection.item() 方法
item 方法根据给定的索引返回集合中对应的节点。索引从 0 开始,如果索引超出范围,则返回 null。在 JavaScript 中,更常用也更简洁的方式是使用方括号语法直接访问。
代码示例:两种索引访问方式对比
javascript
// 获取页面中的所有图片元素
const images = document.images; // HTMLCollection
const firstImage = images.item(0); // 使用 item 方法
const secondImage = images[1]; // 使用方括号语法
console.log(firstImage === secondImage); // false,不同的元素
两种访问方式在功能上基本等价,但在索引越界时的行为略有不同。
代码示例:索引越界时的行为差异
javascript
const images = document.getElementsByTagName("img");
console.log(images.item(100)); // 返回 null(索引超出范围)
console.log(images[100]); // 返回 undefined(索引超出范围)
核心结论 :
item方法在非 JavaScript DOM 实现中更加有用,而在 JavaScript 环境中,方括号语法因为简洁而更受开发者青睐。
四、HTMLCollection.namedItem() 方法
namedItem 方法用于根据 id 或 name 属性从集合中获取元素。它会首先尝试匹配 id ,如果未找到,则尝试匹配 name 属性。如果没有找到匹配的元素,则返回 null。
代码示例:namedItem 匹配 name 属性
javascript
// HTML 结构假设如下:
// <form id="loginForm">
// <input name="username" type="text">
// <input name="password" type="password">
// </form>
const form = document.getElementById("loginForm");
const usernameInput = form.children.namedItem("username");
console.log(usernameInput.type); // "text"
在 JavaScript 中,方括号语法与 namedItem 方法等价。
代码示例:方括号语法与 namedItem 等价
javascript
const form = document.getElementById("loginForm");
const byMethod = form.children.namedItem("password");
const byBracket = form.children["password"];
console.log(byMethod === byBracket); // true
下面的完整示例展示了 namedItem 方法在实际 HTML 结构中的应用:
代码示例:namedItem 的完整应用
html
<div id="personal">
<span name="title">Dr.</span>
<span name="firstname">John</span>
<span name="lastname">Doe</span>
<span id="degree">(MD)</span>
</div>
javascript
const container = document.getElementById("personal");
// 通过 name 属性获取
const titleSpan = container.children.namedItem("title");
// 通过方括号语法获取(等价于 namedItem)
const firstnameSpan = container.children["firstname"];
const lastnameSpan = container.children.lastname;
// 通过 id 属性获取
const degreeSpan = container.children.namedItem("degree");
const output = document.createElement("div");
output.textContent = `Result: ${titleSpan.textContent} ${firstnameSpan.textContent} ${lastnameSpan.textContent} ${degreeSpan.textContent}`;
container.insertAdjacentElement("afterend", output);
// 输出:Result: Dr. John Doe (MD)
⚠️ 【重点 / 面试考点】
namedItem的匹配顺序 :先匹配id,再匹配name,两者都找不到返回null- 方括号语法
collection["name"]与namedItem("name")完全等价 - 也可以通过点语法访问
collection.name,但受限于属性名合法性 - HTML ID 中可能包含冒号和句点,此时必须使用方括号语法
- HTML5 允许纯数字 ID,但
HTMLCollection无法识别纯数字 ID(会与索引访问冲突)
五、HTMLCollection 的即时更新特性
HTMLCollection 是即时更新的,这一点在动态操作 DOM 时需要特别留意。当底层文档结构发生变化时,HTMLCollection 会自动反映这些变化,无需重新获取。
代码示例:HTMLCollection 的自动更新
javascript
const list = document.getElementById("myList");
const items = list.getElementsByTagName("li");
console.log("初始数量:", items.length); // 假设为 3
// 动态添加新的列表项
const newItem = document.createElement("li");
newItem.textContent = "新项目";
list.appendChild(newItem);
console.log("更新后数量:", items.length); // 变为 4,自动更新
这个特性虽然方便,但在遍历同时修改 DOM 结构时会引发问题。
代码示例:错误做法 ------ 遍历时删除
javascript
const items = document.getElementsByClassName("removable");
// 错误的做法:遍历时删除会导致跳过元素
for (let i = 0; i < items.length; i++) {
items[i].remove(); // 每次删除后 length 减小,会跳过部分元素
}
正确的做法是先将 HTMLCollection 转换为静态数组,再进行遍历和删除。
代码示例:正确做法 ------ 先转数组再操作
javascript
const items = document.getElementsByClassName("removable");
const itemsArray = Array.from(items);
itemsArray.forEach(item => item.remove());
六、HTMLCollection 属性的特殊访问方式
HTMLCollection 会将成员的名称和索引直接以属性的形式暴露。这意味着可以通过属性名直接访问集合中的特定元素,而无需调用 item 或 namedItem 方法。
代码示例:四种访问方式的等价性
javascript
// 假设文档中有一个 id 为 myForm 的表单
const forms = document.forms; // HTMLCollection
// 以下四种方式等价
const form1 = forms[0];
const form2 = forms.item(0);
const form3 = forms.myForm;
const form4 = forms.namedItem("myForm");
console.log(form1 === form2); // true
console.log(form3 === form4); // true
需要注意的是,HTML ID 中可能包含冒号和句点这类在属性名中合法的字符,此时必须使用方括号语法进行访问。
代码示例:特殊字符 ID 的方括号访问
javascript
// 假设存在 id 为 "named.item.with.periods" 的表单
const specialForm = document.forms["named.item.with.periods"];
console.log(specialForm);
核心结论 :HTML5 虽然允许使用纯数字的 ID,但
HTMLCollection无法识别纯数字 ID,因为这会与数组形式的索引访问产生冲突。
七、NodeList 与 HTMLCollection 的区别
NodeList 是另一种常见的类数组集合,通常由 childNodes 属性或 querySelectorAll 方法返回。NodeList 分为两种:静态 NodeList 和 动态 NodeList。
querySelectorAll 返回的是静态 NodeList,它的内容不会随 DOM 变化而更新。
代码示例:静态 NodeList 的不变性
javascript
const staticList = document.querySelectorAll("div");
console.log("初始数量:", staticList.length);
// 动态添加一个 div
const newDiv = document.createElement("div");
document.body.appendChild(newDiv);
console.log("更新后数量:", staticList.length); // 不变,仍是初始值
而 childNodes 返回的是动态 NodeList,会随 DOM 变化而更新。
代码示例:动态 NodeList 的自动更新
javascript
const parent = document.getElementById("container");
const childList = parent.childNodes;
console.log("初始数量:", childList.length);
const newChild = document.createElement("span");
parent.appendChild(newChild);
console.log("更新后数量:", childList.length); // 增加了 1
NodeList 同样拥有 item 方法和 length 属性,但只有静态 NodeList 可以使用 forEach 方法进行遍历。
代码示例:静态 NodeList 的 forEach 遍历
javascript
const nodeList = document.querySelectorAll("p");
nodeList.forEach(node => {
console.log(node.textContent);
});
八、NodeList.item() 方法
NodeList 的 item 方法根据给定的索引返回对应的 Node 对象。索引从 0 开始,如果索引越界,该方法不会抛出异常,只会返回 null。
代码示例:item 方法的索引访问
javascript
const tables = document.getElementsByTagName("table");
const firstTable = tables.item(0); // 第一个表格
const secondTable = tables.item(1); // 第二个表格
const outOfRange = tables.item(999); // null
console.log(firstTable); // 第一个 table 元素或 null
console.log(outOfRange); // null
与 HTMLCollection 类似,在 JavaScript 中同样可以使用方括号语法简写。
代码示例:方括号语法的等价写法
javascript
const tables = document.getElementsByTagName("table");
const firstTable = tables[0]; // 与 tables.item(0) 等价
核心结论 :
item方法是NodeList对象本身的方法,而不是 DOM 元素或 DOM 节点的方法。这一点在查看 API 文档时需要区分清楚。
⚠️ 【重点 / 面试考点】
| 对比项 | HTMLCollection | NodeList(静态) | NodeList(动态) |
|---|---|---|---|
| 获取方式 | getElementsBy* / document.forms 等 |
querySelectorAll 返回 |
childNodes 返回 |
| 是否实时更新 | ✅ 即时更新(live) | ❌ 静态,不随 DOM 变化 | ✅ 即时更新(live) |
是否支持 forEach |
❌ 不支持 | ✅ 支持 | ✅ 支持(现代浏览器) |
| 元素类型 | 仅 Element |
包含所有 Node 类型 |
包含所有 Node 类型 |
item 越界返回值 |
null |
null |
null |
是否有 namedItem |
✅ 有 | ❌ 没有 | ❌ 没有 |
九、HTMLCollection 与 NodeList 的遍历方式对比
HTMLCollection 和 NodeList 虽然都是类数组对象,但它们的遍历方式存在差异。
对于 HTMLCollection,由于它没有 forEach 方法,通常使用 for 循环或 for...of 循环。
代码示例:HTMLCollection 的两种遍历方式
javascript
const collection = document.getElementsByClassName("item");
// 方式一:传统 for 循环
for (let i = 0; i < collection.length; i++) {
console.log(collection[i].textContent);
}
// 方式二:for...of 循环
for (const element of collection) {
console.log(element.textContent);
}
对于 NodeList,特别是 querySelectorAll 返回的静态 NodeList,可以直接使用 forEach 方法。
代码示例:NodeList 的 forEach 遍历
javascript
const nodeList = document.querySelectorAll(".item");
nodeList.forEach((node, index) => {
console.log(`第 ${index} 个:${node.textContent}`);
});
如果需要对 HTMLCollection 使用数组方法,可以先将其转换为真正的数组。
代码示例:转为数组后使用数组方法
javascript
const htmlCollection = document.getElementsByClassName("item");
const array = Array.from(htmlCollection);
// 现在可以使用所有数组方法
const textContents = array.map(el => el.textContent);
const filtered = array.filter(el => el.dataset.active === "true");
array.forEach(el => console.log(el.className));
十、实际应用场景与最佳实践
理解 HTMLCollection 和 NodeList 的特性后,在实际开发中可以遵循以下最佳实践。
第一,选择合适的获取方法 。如果不需要动态更新,优先使用 querySelectorAll,因为它返回的静态 NodeList 更安全且支持 forEach。
代码示例:推荐优先使用 querySelectorAll
javascript
// 推荐:静态集合,不受后续 DOM 操作影响
const items = document.querySelectorAll(".item");
第二,在需要动态更新的场景下 使用 getElementsByClassName 或 getElementsByTagName,但遍历时要先创建副本。
代码示例:动态集合先创建副本
javascript
// 需要动态反映 DOM 变化时
const liveItems = document.getElementsByClassName("dynamic-item");
// 迭代前创建副本
[...liveItems].forEach(item => {
// 安全操作
});
第三,使用 namedItem 或方括号语法 快速访问特定元素时,注意 id 优先于 name 的匹配顺序。
代码示例:通过 elements 快速访问表单控件
javascript
const form = document.forms.myForm;
const input = form.elements["email"]; // 先匹配 id 再匹配 name
第四,在性能敏感的场景下 ,尽量减少对 HTMLCollection 的 length 属性的重复访问,将其缓存到局部变量中。
代码示例:缓存 length 提升性能
javascript
const collection = document.getElementsByTagName("div");
const len = collection.length;
for (let i = 0; i < len; i++) {
// 对 collection[i] 进行操作
}
十一、总结与实践要点
HTMLCollection 和 NodeList 是 DOM 编程中频繁遇到的类数组集合,掌握它们的特性是高效操作 DOM 的基础。回顾本文的关键知识点:
HTMLCollection是即时更新的类数组对象,文档结构变化会自动反映到集合中length属性返回集合元素数量,常用于判断空集合和循环控制item方法按索引获取元素,方括号语法是更简洁的替代方式namedItem方法按id或name属性获取元素,id优先匹配- 遍历
HTMLCollection时应先创建副本,避免因即时更新导致意外行为 NodeList分为静态和动态两种,querySelectorAll返回静态,childNodes返回动态NodeList的item方法在索引越界时返回null- 静态
NodeList支持forEach方法,HTMLCollection不支持
获取方式与集合类型关系图
获取 DOM 元素集合
getElementsByTagName
getElementsByClassName
document.forms/images
querySelectorAll
childNodes
HTMLCollection
即时更新
NodeList
静态
NodeList
动态更新
✅ 文档总结
HTMLCollection是即时更新 的类数组对象,getElementsBy*系列方法均返回此类型NodeList分为静态 (querySelectorAll返回)和动态 (childNodes返回)两种HTMLCollection支持item()和namedItem(),不支持forEach;静态NodeList支持forEachnamedItem匹配顺序为id优先于name,都找不到返回null- 遍历时修改 DOM 会导致意外行为,务必先用
Array.from()或[...]创建副本 - 方括号语法
collection[i]与item(i)等价,collection["name"]与namedItem("name")等价 - 性能优化技巧:缓存
length、优先使用querySelectorAll获取静态集合、批量操作前先转数组 - 纯数字 ID 无法通过
HTMLCollection的属性访问获取(与索引冲突)
完整实践示例
javascript
// 综合应用:安全的动态元素操作
const container = document.getElementById("item-list");
// 第一步:获取静态快照(推荐做法)
const items = document.querySelectorAll(".item");
// 第二步:安全遍历,不受 DOM 变化影响
items.forEach((item, index) => {
console.log(`处理第 ${index} 项:${item.textContent}`);
// 即使在此处修改 DOM,也不会影响遍历
if (item.dataset.remove === "true") {
item.remove();
}
});
// 第三步:需要动态反映时使用 HTMLCollection,但先转数组
const liveItems = container.getElementsByClassName("live-item");
const safeArray = Array.from(liveItems);
safeArray.forEach(item => {
item.classList.add("processed");
item.textContent += " [已处理]";
});
// 第四步:通过 namedItem 快速访问
const form = document.forms["myForm"];
const emailInput = form.elements.namedItem("email");
console.log("邮箱值:", emailInput?.value);
通过动手实践,可以更直观地体会到 HTMLCollection 与 NodeList 的区别,以及在不同场景下选择合适集合类型的必要性。
想要解锁更多HTML 核心标签实战、前端零基础入门干货、开发避坑全指南吗?
持续关注,后续将更新CSS 布局实战、JavaScript 交互基础、全站导航开发等硬核内容,带你从新手快速进阶,轻松搞定前端开发!