什么时候实际创建了一个闭包?

时间:2010-05-06 10:45:24

标签: javascript closures

foo的以下情况下是否创建了闭包,而bar却没有?

案例1:

<script type="text/javascript">

    function foo() { }

</script>

foo是一个带有范围链的闭包,只有全局范围。

案例2:

<script type="text/javascript">

    var i = 1;
    function foo() { return i; }

</script>

与案例1相同。

案例3:

<script type="text/javascript">

    function Circle(r) {
        this.r = r;
    }
    Circle.prototype.foo = function() { return 3.1415 * this.r * this.r }

</script>

在这种情况下,Circle.prototype.foo(返回圆圈区域)是指仅具有全局范围的闭包。 (创建此闭包。)

案例4:

<script type="text/javascript">

    function foo() { 
        function bar() { 
        }
    }

</script>

这里,foo是一个仅包含全局范围的闭包,但bar不是闭包(尚未),因为函数foo未在代码中调用,所以没有封闭bar一直在创建。它只会在调用foo时存在,并且闭包bar将存在直到foo返回,然后闭包bar将被垃圾收集,因为没有引用在任何地方都可以。

所以当函数不存在时,不能被调用,不能被引用,那么闭包还不存在(从未创建过)。只有在可以调用或可以引用函数时,才会实际创建闭包?

5 个答案:

答案 0 :(得分:8)

闭包是当某些函数代码中的自由变量被函数“context”绑定到某些值时(闭包是一个比上下文更合适的术语)。

<script type="text/javascript">
    var i = 1;
    function foo() { return i; }
</script>

此处,ifoo的功能代码的自由变量。并且此自由变量不受任何现有上下文(闭包)的任何特定值的约束。所以你没有任何关闭。

<script type="text/javascript">
    var i = 1;
    function foo() { return i; }
    foo(); // returns 1
    i = 2;
    foo(); // returns 2
</script>

现在要创建一个闭包,你必须提供一个值限制的上下文:

<script type="text/javascript">

    function bar() {
       var i = 1;
       function foo() { return i; }
       return foo;
    }
    bar(); // returns function foo() { return i; }
    bar()(); // returns 1
    // no way to change the value of the free variable i => bound => closure
</script>

总之,除非函数返回另一个函数,否则不能有闭包。在这种情况下,返回的函数具有返回函数时存在的所有变量值绑定退出

<script type="text/javascript">

    function bar() {
       var i = 1;
       function foo() { return i; }
       i = 2;
       return foo;
    }
    bar()(); // returns 2
</script>

关于你的例子:

  1. 案例1不是一个闭包,它只是一个函数
  2. 案例2不是一个闭包,它是另一个带有自由变量的函数
  3. 案例3不是一个闭包,它是另一个具有特殊“变量”this的函数。当函数作为对象的成员调用时,该对象被赋值为this。否则,this的值是全局对象。
  4. 案例4不是闭包,它是在另一个函数中定义的函数。如果foo返回bar,您将创建一个仅包含'bar'及其值的闭包:function bar() {}

答案 1 :(得分:1)

关闭栏将存在,直到foo返回,然后关闭栏将被垃圾收集,因为在任何地方都没有任何引用

答案 2 :(得分:0)

这些例子中没有一个是创建的闭包。

第二个会创建一个闭包,如果你真的创建了一个函数并用它做了一些事情,现在你只需创建一个函数然后扔掉它。与添加行3+8;相同,您创建一个数字,然后将其丢弃。

闭包只是一个函数,它在其体内引用其创建环境中的变量,一个典型的例子是加法器:

function createAdder(x) { //this is not a closure
    return function(y) { //this function is the closure however, it closes over the x.
        return y + x;
    }
} //thus createAdder returns a closure, it's closed over the argument we put into createAdder

var addTwo = createAdder(2);

addTwo(3); //3

答案 3 :(得分:0)

如果我可以提供关于何时以及如何创建闭包的模型(这个讨论是理论上的,实际上解释器可以做任何事情,只要最终结果是相同的):只要在期间评估函数,就会创建闭包。执行。然后,闭包将指向执行发生的环境。当站点加载时,Javascript在全局环境中从上到下的顺序执行。所有出现的

function f(<vars>) {
  <body>
}

将变成一个带有和的闭包,带有指向全局环境的指针。同时,在指向此闭包的全局环境中创建引用f

那么在f()在全球环境中执行时会发生什么?我们可以将其视为首先在名称f的全局环境(函数执行的位置)中进行查找。我们发现它指向一个闭包。为了执行闭包,我们创建了一个新环境,其父环境是由闭包f指向的环境,即全局环境。在这个新环境中,我们将f的参数与其实际值相关联。然后在新环境中执行闭包f的主体!任何变量f需求将首先在我们刚刚创建的新环境中解析。如果这样的变量不存在,我们会在父环境中递归地找到它,直到我们到达全局环境。任何变量f创建都将在新环境中创建。

现在,让我们看一下更复杂的例子:

// At global level
var i = 10;                  // (1)
function make_counter(start) {
  return function() {
    var value = start++;
    return value;
  };
}                            // (2)
var count = make_counter(10);    // (3)
count();  // return 10       // (4)
count();  // return 11       // (5)
count = 0;                   // (6)

会发生什么:

在第(1)点:从i10的关联是在全球环境中进行的(其中var i = 10;被执行。

在第(2)点:使用变量(start)和正文return ...;建立一个闭包,指向正在执行它的环境(全局)。然后从make_counter到我们刚刚创建的闭包进行关联。

在第(3)点:发生了几件有趣的事情。首先,我们在全球环境中找到与make_counter相关联的内容。然后我们执行该闭包。因此,创建一个新环境,我们将其命名为CE,它指向由闭包make_counter(全局)指向的环境。然后,我们在start中创建从10CE的关联,并在make_counter中运行关闭主体CE。在这里我们遇到另一个匿名函数。但是,发生的情况与之前相同(召回function f() {}相当于var f = function() {};)。我们使用变量count(空列表)和正文()创建一个闭包,我们将其命名为var ... return value;。现在,这个闭包将指向它正在执行的环境,即CE。这将在以后非常重要。最后,我们有count点指向全局环境中的新闭包(为什么是全局?因为var count ...在全局环境中执行)。我们注意到CE不是垃圾收集的,因为我们可以通过闭包CE到达make_counter,我们可以从变量make_counter从全局环境到达。

在第(4)点,更有趣的事情发生了。我们首先找到与count关联的闭包,这是我们刚刚创建的闭包。然后我们创建一个新的环境,其父级是闭包指向的环境,CE!我们在这个新环境中执行闭包的主体。执行var value = start++;时,我们从当前环境开始搜索变量start,并按顺序向上移动到全局环境。我们在环境start中找到了CE。我们将此start的值(最初10)增加到11。现在start中的CE指向值11。当我们遇到var value时,这意味着不要费心寻找现有的value,只需在正在执行的环境中创建一个变量。因此,建立了从value11的关联。在return value;中,我们查找value的方式与查找start的方式相同。事实证明我们在当前环境中找到它,因此我们不需要查看父环境。然后我们返回这个值。现在,我们刚刚创建的新环境将被垃圾收集,因为我们无法通过全局的任何路径到达此环境。

在第(5)点,同样的事情发生在上面。但现在,当我们查找start时,我们发现值为11而不是10(在环境CE处)。

在第(6)点,我们在全球环境中重新分配count。我们发现现在我们再也找不到从全局到闭合count的路径,反过来又无法找到环境CE的路径。因此,这两个都将被垃圾收集。

P.S。对于那些熟悉LISP或Scheme的人,上面的模型与LISP / Scheme中的环境模型完全相同。

P.P.S。哇,起初我想写一个简短的答案,但结果却是这个庞然大物。我希望我没有犯下明显的错误。

答案 4 :(得分:0)

实际上,经过几年的JavaScript使用和相当彻底的研究,我现在有了一个更好的答案:

每当一个函数出现时,就会创建一个闭包。

因为函数只是一个对象,我们可以更确切地说,每当实例化一个Function对象(函数实例出现)时,就会创建一个闭包。

所以,

function foo() { }

当JS完成上述行的运行时,已经有一个闭包,或者

var fn = function() { };

或者

return function() { return 1; };

为什么呢?因为闭包只是一个带有作用域链的函数,所以在上面的每种情况下,都存在一个函数(它就存在了。你可以调用它(调用它))。它也有一个范围。因此,在我最初的问题(我是OP)中,每个案例1到4,在每个案例中都创建了一个闭包。

案例4是一个有趣的案例。在运行该代码之后,由于foo()存在而导致关闭,但bar()尚未存在(没有调用foo()),因此有一个关闭创建,而不是两个。