JavaScript关闭与匿名函数

我和我的一位朋友正在讨论什么是JS的封闭,什么不是。 我们只是想确保我们确实正确地理解它。

我们来看一下这个例子。 我们有一个计数循环,并希望在控制台上打印计数器变量。 因此,我们使用setTimeout闭包来捕获计数器变量的值,以确保它不会打印N次N值。

错误的解决方案,而闭合或接近闭合 ,以将任何东西:

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

这当然会打印循环后10次i的值,即10。

所以他的尝试是:

for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2);
        }, 1000)
    })();
}

按预期打印0到9。

我告诉他他没有使用封闭来捕捉i ,但他坚持认为他是。 我证明他没有使用闭包 ,将for循环体放入另一个setTimeout (将匿名函数传递给setTimeout ),再次打印10次10​​次。 如果我将它的函数存储在一个var并在循环之后执行,同样也适用于10次打印10,所以这同样适用。所以我的观点是他没有真正捕获i的值 ,使得他的版本不是闭包。

我的尝试是:

for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2);
        }
    })(i), 1000);
}

所以我捕捉到i (在关闭中命名为i2 ),但是现在我返回另一个函数并传递它。 在我的情况下,传递给setTimeout的函数确实捕获了i

现在谁在使用闭包,谁不在?

请注意,两种解决方案都在控制台上打印0到9,因此它们可以解决原始问题,但我们希望了解这两种解决方案中的哪一种使用闭包来实现此目的。


编者注: JavaScript中的所有函数都是闭包,如本文所述。 然而,我们只关心从理论角度确定这些功能的一个子集。 此后,除非另有说明,否则对闭包一词的任何引用都将指代这些功能子集。

关闭的简单解释:

  • 采取功能。 我们称之为F.
  • 列出F.的所有变量。
  • 变量可以有两种类型:
  • 局部变量(绑定变量)
  • 非局部变量(自由变量)
  • 如果F没有自由变量,那么它不能成为闭包。
  • 如果F有任何自由变量(在F 父范围中定义),则:
  • F必须只有一个F的父范围被绑定到一个自由变量。
  • 如果F是从父范围之外引用的 ,则它将成为自由变量的闭包。
  • 这个自由变量被称为闭包F的upvalue。
  • 现在让我们用这个来确定谁使用闭包,谁不使用(为了解释我已经命名了函数):

    案例1:你的朋友的计划

    for (var i = 0; i < 10; i++) {
        (function f() {
            var i2 = i;
            setTimeout(function g() {
                console.log(i2);
            }, 1000);
        })();
    }
    

    在上面的程序中有两个功能: fg 。 让我们看看他们是否关闭:

    对于f

  • 列出变量:
  • i2是一个局部变量。
  • i是一个自由变量。
  • setTimeout是一个自由变量。
  • g是一个局部变量。
  • console是一个自由变量。
  • 找到每个自由变量绑定到的父范围:
  • i 必须在全球范围内工作。
  • setTimeout 绑定到全局范围。
  • console 绑定到全局范围。
  • 在哪个范围内引用了该函数? 全球范围
  • 因此i没有被f 关闭
  • 因此setTimeout没有被f 关闭
  • 因此, console没有被f 关闭
  • 因此函数f不是闭包。

    对于g

  • 列出变量:
  • console是一个自由变量。
  • i2是一个自由变量。
  • 找到每个自由变量绑定到的父范围:
  • console 绑定到全局范围。
  • i2 f
  • 在哪个范围内引用了该函数? setTimeout范围
  • 因此console没有被g 关闭
  • 因此, i2g 关闭
  • 因此函数g是自由变量i2 (它是g的上限)的一个闭包, 它在setTimeout内被引用

    对你不好:你的朋友正在使用闭包。 内部函数是一个闭包。

    案例2:您的计划

    for (var i = 0; i < 10; i++) {
        setTimeout((function f(i2) {
            return function g() {
                console.log(i2);
            };
        })(i), 1000);
    }
    

    在上面的程序中有两个功能: fg 。 让我们看看他们是否关闭:

    对于f

  • 列出变量:
  • i2是一个局部变量。
  • g是一个局部变量。
  • console是一个自由变量。
  • 找到每个自由变量绑定到的父范围:
  • console 绑定到全局范围。
  • 在哪个范围内引用了该函数? 全球范围
  • 因此, console没有被f 关闭
  • 因此函数f不是闭包。

    对于g

  • 列出变量:
  • console是一个自由变量。
  • i2是一个自由变量。
  • 找到每个自由变量绑定到的父范围:
  • console 绑定到全局范围。
  • i2 f
  • 在哪个范围内引用了该函数? setTimeout范围
  • 因此console没有被g 关闭
  • 因此, i2g 关闭
  • 因此函数g是自由变量i2 (它是g的上限)的一个闭包, 它在setTimeout内被引用

    对你有好处:你正在使用闭包。 内部函数是一个闭包。

    所以你和你的朋友都在使用闭包。 不要争论。 我希望我能清除闭包的概念,以及如何为你们俩识别它们。

    编辑:一个简单的解释,为什么所有的函数关闭(积分@Peter):

    首先让我们考虑下面的程序(这是控制):

    lexicalScope();
    
    function lexicalScope() {
        var message = "This is the control. You should be able to see this message being alerted.";
    
        regularFunction();
    
        function regularFunction() {
            alert(eval("message"));
        }
    }

    根据closure定义:

    “闭包”是一个表达式(通常是一个函数),可以将自由变量与一个绑定这些变量的环境 (即“关闭”表达式)一起使用。

    如果你定义了一个函数,它使用了一个在函数之外定义的变量,那么你正在使用closure 。 (我们称该变量为一个自由变量 )。
    他们都使用closure (即使在第一个例子中)。


    简而言之, Javascript Closures允许函数访问 在词法父函数中声明 的变量

    让我们看看更详细的解释。 为了理解闭包,了解JavaScript如何确定变量非常重要。

    领域

    在JavaScript范围内用函数定义。 每个函数都定义一个新的范围。

    考虑下面的例子;

    function f()
    {//begin of scope f
      var foo='hello'; //foo is declared in scope f
      for(var i=0;i<2;i++){//i is declared in scope f
         //the for loop is not a function, therefore we are still in scope f
         var bar = 'Am I accessible?';//bar is declared in scope f
         console.log(foo);
      }
      console.log(i);
      console.log(bar);
    }//end of scope f
    

    调用f打印

    hello
    hello
    2
    Am I Accessible?
    

    现在让我们考虑一下在另一个函数f定义了函数g的情况。

    function f()
    {//begin of scope f
      function g()
      {//being of scope g
        /*...*/
      }//end of scope g
      /*...*/
    }//end of scope f
    

    我们将调用f词汇父 g 。 如前所述,我们现在有2个范围; 范围f和范围g

    但是一个范围是在另一个范围内的“范围内”,父功能范围的子功能部分的范围也是如此? 在父函数范围内声明的变量会发生什么情况; 我能够从子功能的范围访问它们吗? 这正是关闭的地方。

    关闭

    在JavaScript中,函数g不仅可以访问在范围g声明的任何变量,还可以访问在父函数f的范围内声明的任何变量。

    考虑以下内容;

    function f()//lexical parent function
    {//begin of scope f
      var foo='hello'; //foo declared in scope f
      function g()
      {//being of scope g
        var bar='bla'; //bar declared in scope g
        console.log(foo);
      }//end of scope g
      g();
      console.log(bar);
    }//end of scope f
    

    调用f打印

    hello
    undefined
    

    让我们看看console.log(foo);这一行console.log(foo); 。 此时我们在范围g并且我们试图访问在范围f声明的变量foo 。 但是如前所述,我们可以访问任何在这里就是这样的词法父函数中声明的变量; gf的词汇父母。 所以hello打印。
    现在我们来看一下console.log(bar); 。 此时我们在范围f ,我们试图访问在范围g声明的变量barbar在当前范围内未声明,并且函数g不是f的父级,因此bar未定义

    实际上,我们也可以访问在词汇“祖父”函数范围内声明的变量。 因此,如果将有功能h函数内定义g

    function f()
    {//begin of scope f
      function g()
      {//being of scope g
        function h()
        {//being of scope h
          /*...*/
        }//end of scope h
        /*...*/
      }//end of scope g
      /*...*/
    }//end of scope f
    

    那么h将能够访问在函数hgf范围内声明的所有变量。 这是通过关闭来完成的。 在JavaScript中, 闭包允许我们访问在词汇父级函数,词汇盛大父级函数,词汇盛大父级函数等中声明的任何变量。这可以看作是范围链 ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...直到最后一个没有词汇父级的父函数。

    窗口对象

    事实上,链条并不停止在最后的父功能。 还有一个特殊的范围; 全球范围 。 未在函数中声明的每个变量都被认为是在全局范围内声明的。 全球范围有两个专业;

  • 在全球范围内声明的每个变量的访问无处不在
  • 在全局范围中声明的变量对应于window对象的属性。
  • 因此,在全局范围内声明一个变量foo方法有两种。 要么不通过函数声明,要么通过设置窗口对象的属性foo

    这两种尝试都使用闭包

    现在您已经阅读了更详细的解释,现在可以明显看出两种解决方案都使用闭包。 但是可以肯定的是,让我们来证明一下。

    我们来创建一个新的编程语言; JavaScript的无闭幕。 顾名思义,除了不支持闭包,JavaScript-No-Closure与JavaScript完全相同。

    换一种说法;

    var foo = 'hello';
    function f(){console.log(foo)};
    f();
    //JavaScript-No-Closure prints undefined
    //JavaSript prints hello
    

    好的,让我们看看使用JavaScript-No-Closure的第一个解决方案会发生什么;

    for(var i = 0; i < 10; i++) {
      (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2); //i2 is undefined in JavaScript-No-Closure 
        }, 1000)
      })();
    }
    

    因此,这将打印undefined在JavaScript的无盖10倍。

    因此第一个解决方案使用闭包。

    我们来看第二种解决方案;

    for(var i = 0; i < 10; i++) {
      setTimeout((function(i2){
        return function() {
            console.log(i2); //i2 is undefined in JavaScript-No-Closure
        }
      })(i), 1000);
    }
    

    因此,这将打印undefined在JavaScript的无盖10倍。

    两种解决方案都使用闭包。

    编辑:假定这3个代码片段没有在全局范围内定义。 否则,变量fooi将绑定到window对象,因此可以通过JavaScript和JavaScript-No-Closure中的window对象访问。

    链接地址: http://www.djcxy.com/p/1499.html

    上一篇: JavaScript closures vs. anonymous functions

    下一篇: How to loop through a plain JavaScript object with the objects as members?