zoukankan      html  css  js  c++  java
  • JavaScript函数尾调用与尾递归

    • 什么是函数尾调用和尾递归
    • 函数尾调用与尾递归的应用

     一、什么是函数的尾调用和尾递归

    函数尾调用就是指函数的最后一步是调用另一个函数。

     1 //函数尾调用示例一
     2 function foo(x){
     3     return g(x);
     4 }
     5 //函数尾调用示例二
     6 function fun(x){
     7     if(x > 0){
     8         return g(x);
     9     }
    10     return y(x);
    11 }

    调用最后一步和最后一行代码的区别,最后一步的代码并不一定会在最后一行,比如示例二。还有下面这一种不能叫做函数尾调用:

    1 // 下面这种情况不叫做函数尾调用
    2 function fu(x){
    3     var y = 10 * x;
    4     g(y);
    5 }

    为什么这种情况不叫作函数的尾调用呢?原因很简单,因为函数执行的最后一步是return指令,这个指令有两个功能,第一个功能是结束当前函数执行,第二个功能是将指令后面的数据传递出去(函数返回值)。而这个return指令不管有没有声明都会执行,没有声明的情况下返回的数据是undefined,所以上面的代码实际上是以下结构:

    1 function fu(x){
    2     var y = 10 * x;
    3     g(y);
    4     return undefined;
    5 }

    return指令是先关闭函数,然后再返回数据。说到这里,就会引发一个问题出来,如果最后一步不是函数尾调用会怎么样?return指令后面是下面这种情况,会发生什么?

    1 //数的阶乘
    2 function factorial(n){
    3     if(n === 1 || n ===0 ) return 1;
    4     return n * factorial(n - 1);
    5 }

    上面这个数的阶乘算法示例不能叫做函数尾调用,因为最后一步是乘积计算,不是纯粹的函数调用。

     二、函数尾调用与尾递归的应用

    尾调用本质上就是说函数最后执行的一步return指令中,返回数据的这一部分是一个函数执行。看似这个简单的指令和其简单明了的功能,并没有特别之处。但是函数执行时,会在内存形成一个“调用记录”,通常被称为“调用帧”。注意,是在函数执行时内部调用,也就是说是在return指令触发之前的函数调用,因为return指令之后的函数调用会产生一个独立的函数调用栈,而不是在原来的函数调用栈上添加调用帧。

    我们直到浏览器分配的内存空间是有限的资源,也就是说函数的调用栈内存是有限的,如果函数出现很大的循环嵌套调用函数,每个嵌套的函数调用都会在原来的函数调用栈顶上添加一个调用帧,像上面的数的阶乘如果传入的参数是100的话,就会在factorial函数调用栈上产生99个调用帧,如果实参再大一点呢?1000或者更多,这种无限堆叠的可能肯定会带来一个风险,就是栈溢出。

    再来看下面这个示例:

    1 function fb(n){
    2     if(n == 1 || n == 2){
    3         return 1
    4     }
    5     return fb(n - 1) + fb(n - 2);
    6 }
    7 console.log(fb(100)); //堆栈溢出,浏览器崩溃

    上面这个示例(斐波那契数列)有跟乘介算法一样的问题,就是都是在return指令后面对函数执行结果在计算,而这种计算实际上发生当前函数上,而且还会在函数的调用栈上不断增加调用帧,直到符合程序出口逻辑才会停止。但是当计算的数值达到一定程度时就会导致堆栈溢出,造成浏览器奔溃。

    说了这么多,一直没有明确解析什么是尾递归,其实没什么可以解析的,就是在return指令后面调用自身函数执行。然后下面就是使用尾递归和ES的默认参数解决阶乘和斐波那契数列算法的调用帧溢出问题:

     1 //使用ES6的默认值 + 尾递归实现阶乘算法
     2 function factorial1(n,total=1){
     3     if(n === 1 || n === 0 ) return total;
     4     n += 1;
     5     return factorial(n - 1, n * total);
     6 }
     7 //使用ES6的默认值 + 尾递归实现斐波那契数列数列算法
     8 function fb1(n, ac1 = 1, ac2 = 1){
     9     if( n === 1 || n === 2) return ac2;
    10     return fb1 (n - 1, ac2, ac1 + ac2);
    11 }

    在阮一峰老师的《ES6标准入门第三版》P127,中发现老师的两个算法在计算上值都少计算一位,比如老师的阶乘计算5的阶乘结果是24,这个结果一开始令我疑惑不解,个人推断老师的思路是按照计算机的计数方式(从0开始),其参数指定的是阶乘结果的索引,采用参数指定计算值所在结果集合的索引。不知道这个推测是否正确,如果有不对的地方还请各位指正。

    而我在示例中采用的是数值的阶乘结果,不是阶乘结果表中的索引。

  • 相关阅读:
    ubantu安装pip3
    ubantu更换镜像源
    git 快速上手
    python zmq(ZeorMQ)
    用python连接SQL server数据库
    Django模板url需要注意的地方
    希尔排序记录--最好写的排序
    口腔溃疡要对症-------阴虚火旺和阳虚火旺
    与大学室友,保持一定的距离
    取指 间址 执行 中断 FE IND EX INT四个触发器
  • 原文地址:https://www.cnblogs.com/ZheOneAndOnly/p/11368056.html
Copyright © 2011-2022 走看看