JavaScript 中的尾递归

199 阅读1分钟

我们有时会遇到树形节点,而树形节点有时和动态路由有关系。有的时候还需要我们自己找到对应的值。那么怎样从数据节点中找到指定 id 的节点?

这个时候就需要用到 尾递归(tail recursion 或 tail-end recursion)。

尾递归是一种在函数的最后执行递归调用语句的特殊形式的递归。
一些语言提供了尾递归优化。这意味着如果一个函数返回自身递归调用的结果,那么调用的过程会被替换为一个循环,它可以显著提高速度。遗憾的是,JavaScript 当前并没有提供尾递归优化。深度递归的函数可能会因为堆栈溢出而运行失败。

———JavaScript 语言精粹 (2008年版本)

所以,一定要做好容错处理。

回到最开始说的,树形节点的数据结构如下:

var menu = [
    {
        id: '1', 
        children: [
            {id: '1-1'}
        ]
    },
    {
        id: '2', 
        children: [
            {
                id: '2-1'
            },
            {
                id: '2-2',
                children: [
                    {id: '2-2-1'},
                    {
                       id: '2-2-2',
                       children: [
                          {id: '2-2-2-1'},
                          {id: '2-2-2-2'}
                       ]
                    }
                ]
            }
        ]
    }
]

使用尾递归的方式:

function findNode(list, id) {
    list = list || [];
    var allChildren = []; // 保存同级的所有节点
    var node = null; // 存找到的节点
    
    // 这里不建议用 forEach,forEach 没有办法中途 break,会白白的占用主线程。
    for (var i = 0; i < list.length; i++) {
        // 如果找到了退出循环,同时存 node,后面使用
        if (list[i].id === id) {
            node = list[i];
            break;
        }
        allChildren.push(...list[i].children || [])
    }
    
    if (node || allChildren.length === 0) {
        return node; // node 为 找到的值或者 null
    }
    
    return findNode(allChildren, id)
}

运行、测试:

console.log(findNode()) // null
console.log(findNode(menu, '2-2-2-2')) // {id: '2-2-2-2'}
console.log(findNode(menu, '3-3-3-3-3')) // null

如果有错误或者更好的写法,欢迎留言。