尾调用和尾递归

作者: aichisuan | 来源:发表于2019-03-11 19:02 被阅读8次

ES6 有两个新的东西,前端面试的时候偶尔会问道。之前也有在阮一峰的书上看过几次,但是没有统一归纳学习,今天归纳了一下。

尾调用 : 指函数最后一步调用另一个函数;

function f(x){
  return g(x)
};
  • 下面三个都不是属于尾调用
function a(x){
  let y = b(x);
  return y;
}
//调用后有赋值操作不属于尾调用
function c(x){
  return d(x) + 1;
}
//调用后有运算操作
function a(x){
  g(x);
}
//实际代码相当于
/*** 
  function a(x){
    g(x);
    return undefined;
  }
*/
  • 尾调用不一定要出现在函数尾部,只要最后是一步操作即可
function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

尾调用优化

  • 为什么要用尾调用?尾调用优化代码的意义在哪里。
    函数在调用的时候会在调用栈(call stack)中存有一条记录,每一条记录叫做一个调用帧(call frame),每调用一个函数,就向栈中push一条记录,函数执行结束后依次向外弹出,直到清空调用栈。
function a() {
  console.log('如果有帮助请点个赞,大兄弟');
}
function b() {
  a()
}
function c() {
  b()
}
c();
思维类图

造成这样的结果是因为每个函数在调用另一个函数的时候,没有return该调用,所以执行引擎会认为你还没有调用完毕,会保留调用帧。

而如果使用尾调用优化,调用帧就永远只有一条,这个时候就会节省很大一部分的内存空间,维护了代码运行的流畅性。

function a() {
  console.log('如果有帮助请点个赞,大兄弟');
}

function b() {
  return a()
}

function c() {
  return b()
}
c();
image.png

以上代码就叫做尾调用优化,这个时候调用帧就永远只有一条,节省了部分内存。

尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

function test(){
  return test(); //尾递归
}
  • 计算一个数的阶乘,使用尾递归调用的优化
function multiplication(n) {
  if (n === 1) return 1;
  return n * multiplication(n - 1)
}
multiplication(4)//24

//尾递归
function multiplication1(n,init){
  if(n === 1)return init;
  return multiplication1(n -1 ,n*init)
}
multiplication(4,1)//24

//优化
function multiplication2(n,init=1){
  if(n === 1)return init;
  return multiplication2(n -1 ,n*init)
}
multiplication2(4);//24
  • Fibonacci 数列使用尾递归优化

Fibonacci 数列:指的是这样一个数列:/1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*);

//递归形式Fibonacci 数列()
function Fibonacci (n) {
  if ( n <= 1 ) {return 1};

  return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Fibonacci(10) // 89
Fibonacci(100) // 堆栈溢出
Fibonacci(500) // 堆栈溢出

//尾递归优化

function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity 
//在Javascript中,超出 1.7976931348623157E+103088 的数值即为Infinity,
//小于-1.7976931348623157E+103088 的数值为无穷小。

http://es6.ruanyifeng.com/#docs/function
http://www.ruanyifeng.com/blog/2015/04/tail-call.html

相关文章

  • 什么是尾调用?什么是尾递归?尾调用的优化?尾递归优化?

    尾调用优化 尾递归(尾调用优化)

  • 尾调用和尾调用递归

    来自于阮一峰[https://www.ruanyifeng.com/] # ECMAScript 6 入门[ht...

  • 尾调用和尾递归

    尾调用 1. 定义 尾调用是函数式编程中一个很重要的概念,当一个函数执行时的最后一个步骤是返回另一个函数的调用,这...

  • 尾调用和尾递归

    ES6 有两个新的东西,前端面试的时候偶尔会问道。之前也有在阮一峰的书上看过几次,但是没有统一归纳学习,今天归纳了...

  • 尾调用 & 尾递归 & 尾调用优化

    参考 递归和栈帧的调用原理[https://blog.csdn.net/poiuyds/article/detai...

  • 递归调用优化

    尾递归优化 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。 递归非常耗费内存,因为需要同时保存成千上百个调...

  • Kotlin语言(九):特性

    1、尾递归优化 尾递归:函数在调用自己之后没有再执行其他任何操作就是尾递归 尾递归优化的原理就是将递归转换成迭代,...

  • 尾递归

    函数调用自身,称为『递归』;函数尾调用自身,称为『尾递归』 由于递归需要保存大量调用帧,很消耗内存,容易发生 st...

  • 尾调用,尾递归优化

    函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在...

  • python学习

    1:尾递归 解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊...

网友评论

    本文标题:尾调用和尾递归

    本文链接:https://www.haomeiwen.com/subject/ggwbpqtx.html