Ramda递归柯里化函数栈溢出:mapTreeNode2与mapTreeNode差异分析
分析Ramda中mapTreeNode2栈溢出问题及与mapTreeNode的差异
核心问题
mapTreeNode2触发「Maximum call stack size exceeded」的根本原因是递归调用逻辑错误:硬编码了全局处理函数,且递归在函数定义阶段就被立即触发,和mapTreeNode的递归执行机制存在本质差异。
1. 递归参数与执行时机的关键差异
正常工作的mapTreeNode
- 递归时传递的是外层接收的
mapFn参数,保证整个递归链路使用同一个处理函数; map(mapTreeNode(mapFn))是延迟执行逻辑:mapTreeNode(mapFn)返回一个待执行的函数,只有当map遍历子节点时,才会调用该函数处理每个子节点,不会在函数定义阶段触发递归。
触发栈溢出的mapTreeNode2
- 递归时硬编码了全局的
doStuff,而非使用当前传入的mapFn; - 致命错误:
mapTreeNode2(doStuff)在compose定义阶段就被立即执行——JavaScript解释执行时,会先计算compose的所有参数,这导致mapTreeNode2无限递归调用自身,直接撑爆调用栈。
2. 逻辑执行顺序的隐性差异
mapTreeNode的流程:先调用mapFn处理当前节点,再判断原节点是否有子节点,有则用evolve递归处理子节点,递归逻辑依附于子节点的遍历过程;mapTreeNode2用compose组合逻辑(从右到左执行),但错误的递归调用时机打破了流程——还没开始处理节点,就先触发了无限递归。
修正后的mapTreeNode2写法
要修复问题,需保证递归时传递当前的mapFn,并让递归延迟到子节点遍历阶段执行:
const mapTreeNode2 = curry((mapFn) => ifElse( hasChildren, compose( evolve({ children: map(mapTreeNode2(mapFn)) }), // 使用当前传入的mapFn,而非硬编码doStuff mapFn ), mapFn ))
验证修正效果
调用修正后的mapTreeNode2(doStuff)(state),会和mapTreeNode一样正常工作:找到id=6的节点,将其name修改为sixthx,同时保持树形结构的不可变性。
内容的提问来源于stack exchange,提问作者Christine
相关产品推荐
相关产品推荐

