不积跬步之漫谈JavaScript的递归函数

2019-08-25  本文已影响0人  雨飞飞雨

最近在看<JavaScript高级程序设计>中看到arguments.callee这个属性,才知道JavaScript里面的递归有这么多的坑。以前都不知道,今天就整理一下,我们先从最初开始吧。

用递归实现一个阶乘函数

这里就不用说定义了,咱们直接上代码,在正常模式下。

function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num * factorial(num-1);
    }
}

上面是一个典型的递归调用,可但是,它有一个问题。

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));//报错

递归函数就是在一个函数里通过名字调用自身的情况(还是得说一下)。正常的情况下我们这样做没有问题了,可是关键是JavaScript语言和其他的语言不太一样,函数名称只是一个指针,它并不是函数的实体对象。所以如果我们把这个指针改变了,那么在函数里面的指针调用就不是它自己了,上面的例子里,我们直接把它干掉了。让它指向了空,它直接就奔溃了。

这里就引出了一个重要的属性:

arguments.callee

calleearguments对象的一个属性,arguments.callee值一个指向正在执行的函数的指针。因此它可以实现对函数的递归调用。

例如:

function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num * arguments.callee(num-1);
    }
}

通过使用arguments.callee代理函数名,可以确保无论怎样调用函数都不会出问题。因此在正常模式下,使用这种方式会更加保险。

但是在严格模式下,调用上面的arguments.callee却会导致错误,因为严格模式不支持arguments。那怎么样办呢 ?

严格模式下无法使用arguments.callee怎么办 ?

通过使用命名表达式也可是实现同样的效果。看代码:

var factorial = (function f(num){
     if(num<=1){
        return 1;
    }else{
        return num * f(num-1);
    }
});

上面的代码创建了一个名为f()的命名表达式,然后将它赋值给变量factorial。这样的话,即使把变量的变成另一个变量,也不会影响我内部自己的调用。f函数名字仍然有效。所以递归函数调用照样能正确执行。

现在我们把ECMA的版本升级到6.

ES6的情况下呢?

ES6的情况下,我们可以写成另外的方式来实现。例如:

const factorial = (function f(num){
     if(num<=1){
        return 1;
    }else{
        return num * f(num-1);
    }
});

我们把var升级为const,让这个变量变成常量,这样变量也改变不了。我们用箭头函数来替代上面的命名函数。

const factorial =num=>{
     if(num<=1){
        return 1;
    }else{
        return num * factorial(num-1);
    }
}

常量无法更改,所以我们可以写成上面的代码。但是你以为代码就没有问题了吗???

我们这样调用一下:

factorial(100000);

然后报下面的错误,RangeError:超出了最大调用堆栈大小

RangeError: Maximum call stack size exceeded
    at factorial (H:\company_work_space\Demos\Demo1.js:40:18)
    at factorial (H:\company_work_space\Demos\Demo1.js:44:22)
    at factorial (H:\company_work_space\Demos\Demo1.js:44:22)
    at factorial (H:\company_work_space\Demos\Demo1.js:44:22)
    at factorial (H:\company_work_space\Demos\Demo1.js:44:22)
    at factorial (H:\company_work_space\Demos\Demo1.js:44:22)
    at factorial (H:\company_work_space\Demos\Demo1.js:44:22)
    at factorial (H:\company_work_space\Demos\Demo1.js:44:22)
    at factorial (H:\company_work_space\Demos\Demo1.js:44:22)
    at factorial (H:\company_work_space\Demos\Demo1.js:44:22)

下一步就是怎么解决这个问题。答案是使用尾调用优化。
什么是尾调用呢?可以先看这个什么是尾调用?

上一篇下一篇

猜你喜欢

热点阅读