浅谈ES6尾调优化

前言

前两天笔试的时候遇到了这个问题,看了多篇博文之后终于理解了,在这里记录一下~

什么是尾调用

简单来说,一个函数的返回值是对另外一个函数的调用,这种情况就叫做尾调用。

function f1(n) {
    return n * 2
}

function f2(n) {
    return f1(n + 1) //line A
}
const res=f2(2))//line B

在以上代码中,f2的返回值是对f1的调用,这就是尾调用

对尾调用的优化

首先我们要知道尾调优化是浏览器帮我们完成的工作,只要满足特定条件,这种优化就会被触发。

我们都知道:在代码执行时,会产生一个调用栈,调用某个函数时会将其压入栈,当它 return 后就会出栈。
以上面的代码为例我们对ES5和ES6的不同处理方案进行分析。

在ES5中:

  1. 调用函数f2,f2入栈,并记录下调用它的地方,以便知道返回值。
  2. 在尾部调用的函数f1会被推入新栈帧来表示调用,在入栈时记录下调用它的地方。当得到该函数的返回值时,f1执行结束,弹栈。
  3. f2得到了f1的返回值,f2的执行结束了,弹栈。

可以看出在这次调用中,每一个没有被用完的栈帧都保留在内存中。

观察可知,f1的执行结果不必先到达lineA,而是可以直接到达lineB。为什么呢?因为f2的返回值就是f1的返回值,没有在此基础上做任何操作。

在ES6中,就针对这种情况做了优化。

在ES6中:

缩减严格模式里尾调用栈帧的大小,如果满足以下条件,不再创建新栈帧,而是清空并重用当前函数的栈帧:

  1. 尾调用不再访问当前栈帧的变量(函数不是闭包)
  2. 尾调用的语句在函数的最后一行
  3. 尾调用的结果直接作为函数的返回值

如何理解直接呢?例如下面的情况就不会做尾调优化

function f1(n) {
    return n * 2
}

function f2(n) {
    //在f2中并不是直接返回f1的结果,而是要进行加一的操作
    return 1+f1(n + 1) //line A
}
const res=f2(2))//line B

原因也很好理解,因为f1的返回值不能直接到达lineB,而是要先到达lineA进行加1。

尾递归

什么是尾递归呢?其实就是尾调用的特殊形式:当尾调用是对自身的调用时,就是尾递归。

const sum = (n) => {
  if (n <= 1) return n;
  return n + sum(n-1)
}
sum(5)

这段代码的目的是用来计算1到5的和,但是很显然这种情况并不会做尾调优化,因为尾调函数不是直接return,而是要先加n。当n的数字增大的时候,因为之前的调用栈不能被释放掉,会造成栈溢出的问题。

我们可以利用ES6的尾调优化特性对上面的代码进行改写:

const sum = (n, preSum = 0) => {
    if (n <= 1) return n + preSum
    else {
        // 现在的sum是preSum+n,递归算1~n-1的sum
        return sum(n - 1, preSum + n)
    }
}
sum(5)

这样理论上来说没有了最大栈限制的问题,但是我在实际测试(用chrome)是发现还是会有栈溢出的问题,查了之后说是有些浏览器还没支持这种优化。fine~TAT

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值