递归遍历DOM树应聚焦稳定性、可控性与纯净性:只处理元素节点,用children替代childNodes;提供enter/leave钩子解耦逻辑;支持深度限制与自动降级迭代;避免副作用,用WeakMap管理状态。

递归处理 DOM 树本身没有问题,但写法不当容易引发栈溢出、逻辑混乱或性能浪费。关键不是“能不能递归”,而是“怎么递归得更稳、更干净、更可控”。
只处理元素节点,用 children 而非 childNodes
文本节点、注释节点、空白节点会干扰遍历逻辑,也增加判断负担。默认只处理真实 HTML 元素,是最简洁可靠的起点。
- 开头加守卫:if (!node || node.nodeType !== Node.ELEMENT_NODE) return;
- 统一用 node.children 获取子元素——它天然过滤掉非元素节点,无需额外 filter
- 除非明确需要文本内容(如提取纯文本),否则别混用
childNodes;真要用,就显式过滤:Array.from(node.childNodes).filter(n => n.nodeType === Node.TEXT_NODE)
提供 enter/leave 钩子,把业务逻辑和遍历解耦
遍历函数不该写死具体操作,比如“给每个 div 加 class”或“收集所有 input 的 value”。它只负责走树,动作由外部传入。
- 函数签名建议为:walk(node, { enter, leave, maxDepth = Infinity })
- enter 在进入节点时执行(前序),适合初始化、收集、修改样式等
- leave 在子节点全部处理完后执行(后序),适合汇总、清理、计算父级状态
- 深度控制可防栈溢出:if (depth >= options.maxDepth) return;,并在递归调用前递增 depth
深层嵌套时自动降级为迭代
超过 100 层的 DOM 树虽不常见,但一旦出现,递归必崩。与其让整个流程失败,不如提前切换策略。
立即学习“Java免费学习笔记(深入)”;
- 不依赖尾调用优化(V8 未实际启用),也不用
setTimeout拆分——会破坏顺序且引入延迟 - 推荐用 显式栈 + while 循环:维护一个数组模拟调用栈,每个元素包含 node 和 depth
- 可在递归函数中检测当前 depth > 90 时,改用迭代方式继续处理剩余子树
- 迭代版本需自行管理上下文(如路径、累积数据),结构比递归稍复杂,但完全规避栈限制
避免副作用,保持遍历函数纯净
遍历函数的职责是访问节点,不是修改节点。直接在函数体内改 node.style 或 node.classList 会让逻辑耦合、难以复用、不便测试。
- 所有 DOM 修改、状态更新都交给 enter/leave 回调 处理
- 不要修改传入的
node或其属性(如node.dataset.processed = true) - 若需标记已处理节点,可用 WeakMap 存储外部状态,避免污染 DOM
- 这样函数可安全复用:同一棵树,一次加 class,一次收集 id,一次统计层级,互不干扰


















