递归与尾递归

递归

递归的概念大家应该都知道吧,平时开发中也经常用到,比如在做后台管理系统时路由菜单,就需要递归遍历嵌套的路由菜单。最经典的还是阶乘或累加函数,如:

function add(n){
    if(n <= 0) return 0
    return add(n-1) + n
}
add(4) // 10

总结一下有两个特点:

  • 函数自己调用自己
  • 要有临界出门条件,不能无限制的调用

尾调用

直接举一些例子看注释吧

// 不是尾调用,函数的最后一步虽然是一个函数调用,但是并没有return
function fn(n){
    gn(n)
}
// 不是尾调用,函数执行到最后一步,要等gn执行完毕得到结果后再计算才返回结果
function fn(n){
    return gn(n) + 1
}
// 不是尾调用,因为在最后是个赋值操作,所以会先等gn执行完毕后fn才执行
function fn(n){
    var res = gn(n)
    return res
}
// 尾调用,函数最后一步返回一个函数
function fn(n){
    return gn(n)
}
// 尾递归,函数最后尾调用自身
function fn(n){
    if(n === 0) return 0
    return fn(n-1)
}

总结一下

  • 函数内部的最后一步要返回一个函数调用
  • 函数尾调用自身则成为尾递归

区别

看起来递归和尾递归长的都差不多,可实际上差别可大了,特别是性能上。

其实就是执行上下文栈变化不一样,我们来模拟一下,看看下面这两个例子的区别,我们定义执行上下栈是一个数组

stack = [] //执行栈
//尾调用
function fn(x){
    return gn(x)
}
//非尾调用
function fn(x){
    return gn(x) + 1
}

首先模拟一下尾调用:

// 伪代码
stack.push(<fn> functionContext)
stack.pop()
stack.push(<gn> functionContext)
stack.pop()

再模拟一下非尾调用

//伪代码
stack.push(<fn> functionContext)
stack.push(<gn> functionContext)
stack.pop()
stack.pop()

根据伪代码可以很容易的看到最大的区别就是stack的长度变化了。

我们每次调用函数时,都会把其推入执行栈中,执行完毕才会释放,在上面的***非尾调***用函数中最后一句是gn(x) + 1,这是需要等到gn函数执行完得出结果后再运算后才算fn函数执行完毕,即执行栈中fn还没释放又推入了gn。 而在***尾调用***中,最后是return gn,表示fn已经执行完毕出栈了,所以执行栈内永远只有一个函数。如果在大量的递归中,可能就会纯正堆栈溢出的情况了,而用尾递归就不会

用尾递归优化代码

回到最开始的累加函数,我们使用尾递归来优化一下

function add(n, sum){
    if(n <= 0) return sum
    return add(n-1, n + sum)
}
add(4,0) // 10

每次递归调用的时候,把结果当做参数传给下一次调用。

到这里就可以结束了,

然而…对于有小小强迫症的我来说,为什么调用add的时候要多传一个0,这有点难受~~,有几种方法美化一下

  • 使用工厂函数

    let newAdd = function(n) {
        return add(n,0)
    }
    newAdd(4)
    
  • 使用es6的默认参数

    function add(n, sum = 0){
        if(n <= 0) return sum
        return add(n-1, n + sum)
    }
    add(4) // 10
    
  • 使用柯里化

let curry = (fn, sum) => n => fn(n, sum)
let newAdd = curry(add, 0)
newAdd(4) // 10

over~~

  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值