2

经过大量阅读和 hack,我觉得我终于开始了解 JavaScript 闭包及其用途了。然而,我读过的一些资源的措辞对我来说似乎有点矛盾。或者,也许我只是读得太多了。

helephant.com(链接)的一篇优秀文章指出:

当嵌套在另一个函数中的函数从其父范围访问变量时,就会创建闭包。

和...

闭包实际上是在外部函数退出时创建的,而不是在创建内部函数时创建的。

在给出的示例的上下文中,我理解这两点。

但是,来自 John Resig 网站(链接)的更基本的教程指出,下面的代码块中有一个闭包。

var num = 10;

function addNum(myNum){
  return num + myNum;
}

addNum(5);

我见过的几乎所有有用的闭包示例都会返回对内部函数的引用,并稍后对其进行处理。所以这个例子似乎有点毫无意义,但无论如何,让我们尝试接受并理解它仍然是一个闭包。但是,当我尝试整合 helephant 的概念(在外部函数退出之前不会创建闭包)并对其进行一些修改时,我想出了这个:

function outerFunction() {
    var num = 10;

    function addNum(myNum){
      return num + myNum;
    }

    alert(addNum(0));

    num = 5;
}

outerFunction();

现在,根据 Resig 的说法,addNum创建了一个闭包。根据 helephant 的说法,在outerFunction返回之前不会创建闭包。但是......闭包(如果它真的是一个闭包)在退出时使用numfrom before的值。outerFunction矛盾?

不可否认,因为我在退出之前调用 了......它使用当前值似乎是合乎逻辑的。但这让我质疑 Resig 的说法,即他提出的简单示例确实是一个闭包。但是……我有什么资格质疑雷西格?我肯定误会了什么?addNumouterFunctionnum

为了使这更适合问答形式,以下是我总结的问题:

(1) Resig 的例子(以及我的扩展)是一个闭包吗?

(2) 如果是,为什么它使用外部作用域返回之前封闭的 var 的值?

4

5 回答 5

2

所以这并不像对语义的争论那样矛盾。似乎您对情况有了很好的了解,您对闭包如何工作的描述似乎是准确的。

来自其 Wikipedia 条目(最初来自 Sussman 和 Steele。“方案:扩展 lambda 演算的解释器”。)

在计算机科学中,闭包(也称为词法闭包或函数闭包)是函数或对函数的引用以及引用环境——存储对该函数的每个非局部变量(也称为自由变量)的引用的表. 与普通函数指针不同,闭包允许函数访问这些非局部变量,即使在其直接词法范围之外调用也是如此。

因此,从技术上讲,闭包只是一个具有引用环境的函数(对 javascript 中外部函数范围的引用)。但它的特别之处在于它可以从另一个作用域调用。

所以从技术上讲,Resig 的例子是一个闭包。它是一个引用外部环境的函数。它的外部环境恰好是全局范围,但它仍然有一个。但在通过之前,它与其他功能相比是不可区分/特殊的。

最后,将这些示例中的任何一个称为闭包都没有错。但是为了使它们与通用函数相比有用,您将希望将其从调用上下文中传递出去。

于 2013-03-29T04:06:05.307 回答
1

所以用闭包来证明的一点是,即使在外部函数的作用域之外,内部函数也可以挂在外部函数的变量上。

内部函数通过 Javascript 的原型继承继承了外部函数的变量和值的副本,并且第二个变量分配尚未在外部函数的范围内进行评估,因此内部函数将继承当前分配的值的副本。

很好地展示了这种行为:

http://jsfiddle.net/JD32X/

function outer()
{
    var test = "hello";
    var blah = function() { window.alert(test); };
    blah();
    test = "not hello";
    blah();
}
outer();
于 2013-03-29T04:09:38.000 回答
1

window实际上本身就有一个功能范围。

window存在于函数范围 ( var bob = "Bob";) 和对象属性 ( window.bob = "Bob";) 中。在典型用法中,它们是相同的,但实际上并非如此。

window.bob = "Bob"
delete window.bob;
window.bob; // undefined

var bob = "Bob"
delete window.bob;
bob; // "Bob"

因此,只要您运行的范围可以访问外部函数的范围,就会发生闭包。

我不会说window提供闭包的原因只是它window是全局可访问的。
也就是说,网站上的每一段代码都可以访问window的函数范围。
因此,它并不是真正的“封闭”,因为您没有阻止访问它。

您可以从中得到的是,依赖于全局范围的示例与构建闭包的技术相同,因此是开始掌握它们的最简单和最通用的方法......

...但是,因为这些值并没有对外界隐藏,所以在事实之后(通过外部函数返回),没有真正的闭包发生。

于 2013-03-29T04:06:21.357 回答
0

@戴夫约翰逊,你好。让我试着解释一下为什么这The closure is actually created when the outer function exits, not when the inner function is created是一个真实的陈述。

当您在某个范围(函数)内定义新变量时,会创建此变量并lives在此范围内。当函数返回时,发生的正常情况是没有对该变量的引用,所以它不应该再存在了。它将被“垃圾收集”。但是,如果某个内部函数(作用域)使用此变量并在外部作用域返回后保持对它的引用- 将创建一个闭包。因此,据说The closure is actually created when the outer function exits, not when the inner function is created.

在您的示例中:

function outerFunction() {
    var num = 10;
    function addNum(myNum){
        return num + myNum;
    }
    alert(addNum(0));
    num = 5;
}
outerFunction();

num 变量在调用 outerFunction 时创建,在函数返回时完全释放。为什么?因为在那之后没有对该对象的引用。实际上,内部函数 addNum 也是在该范围内创建的,并且在 outerFunction 返回后将不存在。所以我在这里看不到真正的关闭

编辑: 我们在这里看到的只不过是SCOPING. 是的 - 在 addNum 函数内部,对来自外部范围的变量的引用是 MADE ,但该变量不是enclosed. 例如,enclosed如果只是在代码中的那个位置将 addNum 函数作为事件处理程序分配给按钮单击事件。这样,作为对象的函数本身将在 outerFunction 返回后继续存在,因为对该函数的引用将存在于按钮单击事件中,在这种情况下,对num变量的引用也将存在。


如果您以这种方式创建 addNum 函数:

...
window.addNum = function(myNum) {
     return num + myNum;
}
...

那将是完全不同的情况。这里创建了一个闭包,但可以说,这是在外部函数返回时发生的。为什么?因为那是变量应该被“垃圾收集”的时刻num,这不会发生,因为仍然有对该对象的引用。

关于什么是闭包有一个很好的解释。想象你的外在范围是一场婚姻。这个范围实际上是你和家人一起住的房子——你有一个妻子、两个孩子、家具等。它们都是在婚姻“范围”内创建的属性和功能。当你被吞噬时,你会被踢出你的房子('marriage' 函数已经返回),但你仍然保留对在你的婚姻中创建的对象的引用,这些对象将是你的两个孩子:)。至于你的妻子——她将不再作为妻子存在于你自己的“范围”中,你的家具也将不再属于你:)

更新
我首先要向您解释的是'The closure is actually created when the outer function exits, not when the inner function is created' 在您的示例中适用于 addNum 函数,但不适用于 outerFunction,因为在 outerFunction 退出后没有创建闭包。

更新 2 - 这不是包含一个值,而是一个引用

// man is not the value "Jonh Resig"
// it's a pointer to SOME 'man', existing in the memory
var man = "John Resig";

function haveSex(woman) {
     return man + woman; // creating a baby;
}
haveSex("Some Girl");      // baby of "Some Girl" and Resig
man = "Douglas Crockford"; // changing `man` reference to point to another object
haveSex("Some Girl");      // baby of "Some Girl" and Crockford
于 2013-03-29T09:37:10.380 回答
0

Ben McCormick 是正确的,我只是想扩展他的答案并解释他所写的内容如何适用于 helephant 文章(但我没有足够的声誉点来添加评论)。

当 helephant 写下那条评论时,他是在提出一个具体的观点。用我自己的例子来说明同样的观点,考虑下面的代码示例——我们循环创建 10 个函数,将它们放在一个数组中,然后循环遍历数组并调用它们:

function getFunctions() {
    var functions = [];
    for (var i=0; i < 10; i++) {
        functions.push(function() { window.alert(i); });
    }
    return functions;
}
functionArray = getFunctions();
for (var j=0; j < functionArray.length; j++) {
    var f = functionArray[j];
    f();
}

Helephant 警告你不要混淆:这会提醒数字“10”十次。它不会提醒 1、2、3、... 10。但他的术语和解释并不完全准确。执行时function() { window.alert(i); },系统会创建一个闭包。正如 Ben McCormick 所说,闭包由 2 个指针组成。一指向函数定义(函数的参数和函数体)。另一个指向环境。在我们的示例中,所有 10 个闭包都指向同一个环境。

再一次——从技术上讲,有 10 个闭包,但只有一个环境。这个环境是i绑定到“10”的地方。这应该具有直观的意义。 var i只执行一次,所以只有一个i. function() { window.alert(i); }执行了 10 次,所以有 10 个闭包。但是所有 10 个闭包都指向相同的环境。

于 2016-10-19T02:44:08.630 回答