用迭代器封装DOM节点处理,核心是分离遍历与业务逻辑;NodeIterator天然支持深度优先、类型过滤与自定义筛选;封装成类后提供链式API和Symbol.iterator兼容for...of;需规避DOM动态修改、空父元素访问及强制回流等陷阱。

用迭代器封装 DOM 节点处理,核心是把“遍历逻辑”和“业务逻辑”分开,让代码更清晰、可复用、易测试。不是为用而用,而是当节点筛选条件变多、顺序要求明确、或需支持暂停/恢复/过滤时,原生 for 循环或 querySelectorAll 就显得力不从心。
用 NodeIterator 精准提取文本节点
浏览器原生的 NodeIterator 天然适合深度优先遍历,且自动跳过非目标节点类型:
- 设置
NodeFilter.SHOW_TEXT,它只返回 Text 类型节点,不用手动判断node.nodeType === 3 - 传入自定义过滤函数,可排除
<script></script>、<style></style>内容,或忽略纯空白文本 - 不递归、不维护栈,DOM 嵌套再深也不怕栈溢出
示例:只取可见、非空、不在 script/style 中的文本节点
const iter = document.createNodeIterator(
document.body,
NodeFilter.SHOW_TEXT,
node => {
const parent = node.parentElement;
if (['SCRIPT', 'STYLE'].includes(parent?.tagName)) return NodeFilter.FILTER_REJECT;
if (!node.textContent.trim()) return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
},
false
);
封装成可复用的迭代器类
把 NodeIterator 包一层,加上链式方法和常用操作,比如跳过、收集、映射、中断条件:
- 提供
next()、all()、filter(fn)、map(fn)等接口 - 内部缓存当前节点,支持多次调用
current获取当前项 - 允许传入起始节点、过滤规则、是否忽略注释等配置
这样后续要“找所有带 data-id 的按钮里的文字”,或“提取 article 下第 2 层 p 的首行文本”,只需组合调用,不用重写遍历逻辑。
结合 Symbol.iterator 实现 for...of 支持
让自定义迭代器兼容原生语法,提升可读性:
- 在类上部署
[Symbol.iterator]()方法,返回一个符合迭代协议的对象 - 每次
next()返回{ value, done },value 是处理后的节点或数据(如 textContent、{el, rect}) - for...of 自动调用,配合 break / continue 控制流程更自然
例如:
for (const text of new TextNodeIterator(document.body)) {
if (text.length > 100) break;
console.log(text);
}
避免常见陷阱
实际封装时容易忽略的细节:
- NodeIterator 遍历时若 DOM 动态修改(如插入/删除节点),行为未定义——建议先快照或冻结遍历范围
- 过滤函数中访问
node.parentElement要判空,因为根节点或孤立文本节点可能没有父元素 - 不要在循环中反复调用
getBoundingClientRect()或offsetHeight,它们会强制回流 - 若需频繁查找+操作,考虑用
DocumentFragment批量更新,而不是逐个修改真实 DOM

















