JavaScript 闭包和递归

4
我一直在更深入地研究编程中的闭包概念,特别是与JavaScript相关的部分。虽然我已经写了多年的JavaScript代码,但我还没有完全理解它与我所写的JavaScript代码有何不同。我也理解递归的概念,但我想知道,闭包和递归有什么相似之处?我是否正确地理解,递归本身就是一种闭包类型?
闭包:
function init() {
    var name = "Stack Overflow";
    function displayName() {
        alert(name);
    }
    displayName();
}
init();

递归:

function factorial(num) {
    if(num < 0)
        return -1;

    else if(num == 0)
        return 1;

    else
        return (num * factorial(num - 1));
}

alert(factorial(8));

我觉得我开始理解闭包只是在一个函数内部再定义一个函数,并通过作用域使内部函数可以访问外部函数。是否可能有递归闭包?我的递归示例并不完全是闭包的示例,但它至少能发生吗?我试图理解递归和闭包如何相似、不同或者它们是否可以比较。也许有一些例子可以描述这个问题吗?


2
每当您定义一个函数时,就会创建一个闭包。这大概是这两个概念最相似的地方了。 - Denys Séguret
2
闭包是指“封闭在内”的函数,它访问定义在该函数外部的变量。递归是指一个函数(可能是闭包,也可能不是)调用自身。我不会说它们相似或不同;它们彼此无关。 - gen_Eric
1
递归:假设你有一张人手持盒子的图片。在盒子上有同一个人手持着带有图片的盒子的图片。这张图片包含了它自己。 :) http://zh.wikipedia.org/wiki/%E9%81%93%E6%96%87%E6%95%88%E5%BA%94 - akonsu
2
你在帖子中所写的不是一个递归函数。 - akonsu
1
闭包不是函数内部的函数。闭包是一个函数及其自由变量(即外部变量)和它们在函数调用时捕获的值。 - akonsu
显示剩余9条评论
2个回答

11

闭包就是一个函数或者过程,它“封闭了”一个环境(即其主体)。在您的代码中,initdisplayNamefactorial都是闭包。使用JavaScript function 关键字(或现在我们有 ES6 中的 => 箭头函数)当您想要创建一个闭包时。

递归是一个过程反复重复自己的效果。


我一直在阅读一本新书,它谈到了一些相关的话题。如果您感兴趣的话,我想在这里分享一些笔记。

下面是一个用递归计算阶乘的函数。它与您的函数做的事情相同,但方式非常不同。让我们看看!

function factorial(x) {
  if (x < 0) throw Error("Cannot calculate factorial of a negative number");
  function iter(i, fact) {
    return i === 0 ? fact : iter(i-1, i*fact);
  }
  return iter(x, 1);
}

factorial(6); // 720

将其与上面定义的函数进行比较 ^.^。请注意,即使它是递归的,它也不会再次调用自己(factorial)。这使用了一种消耗线性空间和时间的线性迭代过程。函数的评估看起来像这样

factorial(6);
  iter(6, 1);
  iter(5, 6*1); 
  iter(4, 5*6);
  iter(3, 4*30);
  iter(2, 3*120);
  iter(1, 2*360);
  iter(0, 1*720);
// => 720

相比之下,您的函数处理过程将如下所示

factorial(6);
(6 * factorial(5));
(6 * (5 * factorial(4)));
(6 * (5 * (4 * factorial(3))));
(6 * (5 * (4 * (3 * factorial(2)))));
(6 * (5 * (4 * (3 * (2 * factorial(1))))));
(6 * (5 * (4 * (3 * (2 * 1)))));
// => 720

请注意,随着n的增加,n!会添加另一个堆栈调用。这是一个线性递归过程。对于较大的n值,此方法使用更多的空间来计算相同的结果。


你的第一个代码示例真的展示了一个迭代过程吗?我正在仔细查看代码,想象它被运行...调用factorial(N)将导致首先进行N个递归iter()调用,然后进行N个递归返回。是这样吗?那么它将使用与N成比例的堆栈空间。还是我误解了什么? - Roman Vinogradov
它使用尾调用,在ES6中仅被优化以避免堆栈增长。 - Gregory Magarshak

2
我之前曾经在这里提到过闭包的内容(虽然没有提到递归): http://hangar.runway7.net/javascript/guide 但是就递归而言,我认为闭包只有在递归发生时才会被使用,但它们并不一定相关。实际上,我建议您根本不要在递归中使用闭包,因为这可能会导致意外的错误。递归通常依赖于每个函数调用仅使用显式传入的参数的知识来执行,并且闭包违反了这个原则。
另一方面,递归是通过重复调用自身来完成其任务的函数的执行过程。
两个概念都可以通过搜索引擎更好地学习,但请注意它们通常不相关。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接