递归函数必须有明确终止条件,否则栈溢出;树遍历需先判节点有效性再处理子节点;带层级需求应传参而非依赖闭包或全局变量;超深嵌套宜用迭代模拟;react递归渲染需稳定key和memo优化。

递归函数必须有明确的终止条件,否则会无限调用栈溢出
树形结构没有天然的“末尾”,if 判断缺失是最常见崩溃原因。比如遍历菜单树时,忘记检查 children 是否为 null 或 undefined,或误把空数组 [] 当作终止信号——空数组仍需进入递归体处理(哪怕什么也不做),而 null 才该直接 return。
实操建议:
- 始终把「节点是否有效」放在递归体最开头,例如:
if (!node || !node.id) return; - 对子节点数组,先判空再遍历:
if (Array.isArray(node.children) && node.children.length > 0) { node.children.forEach(...); } - 避免在递归调用前修改原数据(如
node.children.pop()),这会干扰后续层级判断
用参数传递上下文,别依赖闭包或全局变量存路径或层级
处理带层级信息的需求(如生成面包屑、计算缩进、限制最大深度)时,闭包捕获的变量会在多层递归中被意外覆盖;全局变量则完全不可重入——同一函数并发调用会相互污染。
实操建议:
- 把当前路径、深度、父ID等作为参数传入下一层:
traverse(node, path = [], depth = 0) - 路径推荐用不可变方式拼接:
traverse(child, [...path, node.id], depth + 1),避免path.push()后忘记pop() - 深度控制加在入口判断里:
if (depth > MAX_DEPTH) return;,比在每层末尾检查更安全
JSON.stringify 会爆栈?用迭代替代递归处理超深嵌套
JSON.stringify 对超过 ~10000 层的对象默认抛 RangeError: Maximum call stack size exceeded,不是你代码写错了,是 V8 引擎栈限制硬伤。此时强行增加栈大小(--stack-size)不现实,应切换策略。
实操建议:
- 改用显式栈模拟递归:
const stack = [{ node: root, depth: 0 }]; while (stack.length) { const { node, depth } = stack.pop(); /* 处理 node */ if (node.children) stack.push(...node.children.map(c => ({ node: c, depth: depth + 1 }))); } - 对纯遍历场景(无需回溯),队列(
shift())比栈(pop())更接近原递归顺序,但性能略差;用push/pop配合反向遍历children可保持顺序 - 若只是序列化,考虑
flatted库或手写循环版JSON.stringify替代方案,它内部用数组代替调用栈
React 中递归渲染组件要小心 key 和 memo 化失效
用 {node.children.map(child => <treenode key="{child.id}" node="{child}"></treenode>)} 看似自然,但若 node.children 是引用变化的新数组(如从 Redux store 派生),即使内容未变,map 返回的新元素数组会让 React 丢弃旧 DOM 节点重渲染整棵子树。
实操建议:
-
key必须基于稳定标识(如child.id),禁用索引index—— 插入/删除会错乱 - 在外层组件用
React.memo时,确保props.node是浅相等稳定的;否则加areEqual自定义比较逻辑 - 深层嵌套更新频繁时,考虑用
useMemo缓存子节点数组:const childrenNodes = useMemo(() => node.children.map(...), [node.children])
实际项目里,最难调试的往往不是递归逻辑本身,而是某次中间状态修改了原始数据结构,导致下一层拿到的是半截脏数据——动手前先用 structuredClone 或 JSON.parse(JSON.stringify()) 做快照,比反复猜哪一行改坏了结构高效得多。










