JavaScript闭包的冲突描述

时间:2013-03-29 03:49:15

标签: javascript closures

经过大量的阅读和黑客攻击后,我觉得我终于开始理解JavaScript闭包及其用途了。然而,我读过的一些资源以某种方式措辞,似乎可能与我略有矛盾。或者,也许我只是在读它们太多了。

来自helephant.com(link)的精彩文章指出:

  

当嵌套在另一个函数中的函数从其父作用域访问变量时,将创建闭包。

和...

  

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

我在给出的例子的背景下理解这两点。

但是,John Resig的网站(link)提供了一个更基本的教程,指出下面的代码块中有一个闭包。

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返回之前不会创建闭包。但是......闭包(如果它确实是一个闭包)在num退出时使用之前的<{1}}值。矛盾?

不可否认,由于我在outerFunction退出之前调用 addNum,因此使用outerFunction的当前值似乎是合乎逻辑的。但这让我质疑Resig的说法,即他提出的简单例子确实是一个封闭。但是......我是谁来质问Resig?当然我误解了什么?

为了使这更符合Q&amp; A格式,我的问题归结为:

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

(2)如果是,为什么在外部范围返回之前使用封闭变量的值?

5 个答案:

答案 0 :(得分:2)

所以这并不像对语义的争论那么矛盾。看起来你对这种情况有了很好的了解,你对闭包工作方式的描述似乎是准确的。

来自its Wikipedia entry(最初来自Sussman和Steele。“Scheme:扩展lambda演算的翻译”。)

  

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

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

从技术上讲,Resig的例子就是封闭。它是一个引用外部环境的函数。它的外部环境恰好是全球范围,但它仍然有一个。但与其他功能相比,它没有区别/特殊,直到它通过。

最后调用这些示例闭包没有错。但是,与通用函数相比,它们是有用的,您可能希望将它传递出调用上下文。

答案 1 :(得分: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的功能范围 因此,它并非真正“封闭”,因为您没有阻止访问它。

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

...但是,因为这些值不会被外界所隐藏,事后(通过外部函数返回),没有真正的闭包发生。

答案 2 :(得分:1)

所以闭包证明的一点是内部函数可以挂在外部函数的变量上,甚至在外部函数的范围之外。

内部函数通过Javascript的prototypical inheritance继承外部函数的变量和值的副本,并且在外部函数的作用域中尚未评估第二个变量赋值,因此内部函数将继承当前的副本分配值。

很好地展示了这种行为:

http://jsfiddle.net/JD32X/

function outer()
{
    var test = "hello";
    var blah = function() { window.alert(test); };
    blah();
    test = "not hello";
    blah();
}
outer();

答案 3 :(得分:0)

@DaveJohnson,你好。 让我试着解释为什么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。如果只是在代码中的那个位置,你将addNum函数作为事件处理程序分配给按钮单击事件,它将是enclosed。这样,函数本身作为对象将在返回outerFunction后继续存在,因为在按钮单击事件中将存在对该函数的引用,并且在这种情况下也将存在对num变量的引用。


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

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

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

关闭什么是一个非常好的解释。想象一下你的外在范围是一种婚姻。这个范围实际上是一个与你的家人住在一起的房子 - 你有一个妻子,两个孩子,家具等。它们都是在婚姻范围内创造的财产和功能。当你被抛弃时,你会被踢出你的房子(函数'婚姻'已经返回),但你仍然保留对婚姻中创建的对象的引用,这些对象将是你的两个孩子:)。至于你的妻子 - 她将不再存在于你自己的“范围”中,因为妻子和你的家具将不再是你的妻子:)

<强>更新
我首先要向您解释的是,示例中的'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

答案 4 :(得分: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); }时,系统会创建一个闭包。正如本麦考密克所说,封闭包括两个指针。一个指向函数定义(函数和函数体的参数)。另一个指向环境。 在我们的示例中,所有10个闭包都指向相同的环境。

再次 - 在技术上非常精确,有10个闭包,但只有一个环境。 此环境是i绑定到“10”的位置。 这应该具有直观意义。 var i只执行一次,因此只有i的一个实例。 function() { window.alert(i); }执行了10次,因此有10个闭包。但是所有10个闭包都指向相同的环境。