js函数式编程——蹦床函数

概述

这是我在学习函数式编程的时候,关于递归尾递归相互递归蹦床函数的一些心得,记下来供之后开发时参考,相信对其余人也有用。javascript

参考资料:JavaScript玩转Clojure大法之 - Trampolinehtml

递归

咱们知道,es5是没有尾递归优化的,因此在递归的时候,若是层数太多,就会报“Maximum call stack size exceeded”的错误。就连下面这个及其简单的递归函数都会报“Maximum call stack size exceeded”的错误。java

function haha(a) {
    if(!a) return a;
    return haha(a-1);
}

haha(100); //输出0
haha(12345678); //输出“Maximum call stack size exceeded”

为何会报“Maximum call stack size exceeded”的错误?我以为缘由是在每次递归调用的时候,会把当前做用域里面的基本类型的值推动栈中,因此一旦递归层数过多,栈就会溢出,因此会报错。编程

注意:闭包

  1. js中的栈只会储存基本类型的值,好比:number, string, undefined, null, boolean。
  2. 为何在调用下一层递归函数的时候没有释放上一层递归函数的做用域?由于在回来的时候还须要用到里面的变量。

尾递归

怎么优化上面的状况呢?方法是使用尾递归。有尾递归优化的编译器会把尾递归编译成循环的形式,若是没有尾递归优化,那就本身写成循环的形式。以下面的例子所示:函数式编程

//尾递归函数,返回一个函数调用,而且这个函数调用是本身
function haha(a, b) {
    if(b) return b;
    return haha(a, a-1);
}

//优化成循环的形式
function yaya(a) {
    let b = a;
    while(b) {
        b = b - 1;
    }
}

须要注意的是,看上面尾递归的代码,有一点很重要,就是用一个b变量来存上一次递归的值。这是尾递归经常使用的方法。另外,其实上面尾递归的代码不须要变量b,但为了便于说明,因此我加上了变量b。函数

相互递归

可是关于递归还有一种形式,就是相互递归,以下面的代码所示:学习

function haha1(a) {
    if(!a) return a;
    return haha2(a-1);
}

function haha2(a) {
    if(!a) return a;
    return haha1(a-1);
}

haha1(100); //输出0
haha1(12345678); //输出Maximum call stack size exceeded

能够看到,当相互递归层数过多的时候,也会发生栈溢出的状况。优化

蹦床函数

蹦床函数就是解决上面问题的方法。es5

首先咱们改写上面的相互递归函数:

function haha1(a) {
    if(!a) return a;
    return function() {
        return haha2(a-1);
    }
}

function haha2(a) {
    if(!a) return a;
    return function() {
        return haha1(a-1);
    }
}

这个改写就是创建一个闭包来封装相互递归的函数,它的好处是因为不是直接的相互递归调用,因此不会把上一次的递归做用域推动栈中,而是把封装函数储存在堆里面,利用堆这个容量更大但读取时间更慢的储存形式来替代栈这个容量小但读取时间快的储存形式,用时间来换取空间

咱们尝试使用一下上面的函数:

haha1(3)(); //输出一个函数
haha1(3)()()(); //输出0

经过上面的示例能够看到,若是参数不是3而是很大的一个数字的时候,咱们就须要写不少个括号来实现调用不少次。为了简便,咱们能够把这种调用形式写成函数,这就是蹦床函数。以下所示:

function trampoline(func, a) {
    let result = func.call(func, a);
    while(typeof result === 'function') {
        result = result();
    }
    return result;
}

基本原理是一直调用,直到返回值不是一个函数为止

最后来使用蹦床函数:

trampoline(haha1, 12345678); //过一下子就输出0

因为储存在堆中,因此耗时较长,过了一下子才会输出0,可是并无报栈溢出的错误

相关文章
相关标签/搜索