JavaScript中DOM集合原生支持迭代器协议,自定义包装器可通过实现[Symbol.iterator]()方法支持for...of;需注意实时集合应转为静态数组以避免DOM变更问题。

JavaScript 中 DOM 元素集合(如 NodeList、HTMLCollection)原生支持迭代器协议(即实现了 [Symbol.iterator]),但如果你需要自定义遍历逻辑(比如只遍历带特定 class 的子元素、跳过注释节点、按深度优先/广度优先重排顺序等),可以为自定义的包装对象实现可迭代协议。
让自定义 DOM 包装器支持 for...of
核心是给对象添加 [Symbol.iterator]() 方法,返回一个符合迭代器协议的对象(有 next() 方法,返回 { value, done })。
例如,封装一个只遍历「可见的、非空文本节点的父元素」的迭代器:
class VisibleElementIterator {
constructor(container) {
this.container = container;
}
<p>*[Symbol.iterator]() {
const walker = document.createTreeWalker(
this.container,
NodeFilter.SHOW_ELEMENT,
{
acceptNode(node) {
// 过滤:显示且有非空文本内容的元素
const text = node.textContent?.trim();
return text && window.getComputedStyle(node).display !== 'none'
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP;
}
}
);</p><pre class="brush:php;toolbar:false;">let node;
while (node = walker.nextNode()) {
yield node;
}} }
// 使用 const iter = new VisibleElementIterator(document.body); for (const el of iter) { console.log(el.tagName, el.textContent?.slice(0, 30)); }
复用原生集合并增强遍历逻辑
不必从头写迭代器,可基于 NodeList 或 Array.from() 预处理,再返回自定义迭代器:
立即学习“Java免费学习笔记(深入)”;
- 先获取所有子元素:
this.container.children或this.container.querySelectorAll('*') - 用
filter()、map()等加工成目标数组 - 在
[Symbol.iterator]中返回该数组的默认迭代器(return arr[Symbol.iterator]())或手动控制
示例:只遍历 data-async="true" 的元素,并按嵌套深度升序排列:
class AsyncElementIterator {
constructor(root) {
this.root = root;
}
<p>[Symbol.iterator]() {
const nodes = Array.from(this.root.querySelectorAll('[data-async="true"]'))
.map(el => ({
el,
depth: el.querySelectorAll('*').length // 粗略估算嵌套深度
}))
.sort((a, b) => a.depth - b.depth)
.map(item => item.el);</p><pre class="brush:php;toolbar:false;">let index = 0;
return {
next() {
if (index < nodes.length) {
return { value: nodes[index++], done: false };
}
return { value: undefined, done: true };
}
};} }
注意 DOM 集合的“实时性”与迭代器兼容性
HTMLCollection 是实时集合(live),NodeList 可能是静态或实时(取决于获取方式),直接在其上实现迭代器需小心 DOM 变更导致的意外行为:
- 推荐对实时集合先转为静态数组:
Array.from(collection)或[...collection] - 若必须响应式遍历(如监听新增元素),应结合
MutationObserver,而非仅靠一次迭代器 - 避免在
next()中反复调用querySelectorAll—— 性能差;建议预计算或缓存
扩展原生类型(不推荐但可行)
虽然不建议污染原生原型,但技术上可以为 NodeList 添加自定义迭代方法(不影响原生 [Symbol.iterator]):
NodeList.prototype.byClass = function(className) {
return {
[Symbol.iterator]: function() {
const filtered = Array.from(this).filter(el => el.classList.contains(className));
let i = 0;
return {
next() {
return i < filtered.length
? { value: filtered[i++], done: false }
: { value: undefined, done: true };
}
};
}
};
};
<p>// 使用
for (const el of document.querySelectorAll('div').byClass('active')) {
el.style.backgroundColor = 'gold';
}
不复杂但容易忽略:自定义迭代器本质是控制「每次 next() 返回什么」,DOM 操作只是数据源。关键是把 DOM 查询 + 过滤 + 排序逻辑封装进迭代过程,保持接口简洁、行为可预测。


















