匿名自执行函数与闭包的联系

896 阅读4分钟

最近在重新理解闭包,看到一篇文章谈到闭包可以模拟块级作用域: 要求是i在for循环外不应该被引用到

function A(){
     for(var i = 0; i < 3; i++){
         console.log(i)
     }
    console.log(i) // 在循环体外依然可以读到i 输出3
 }
 A();

文章给出解决方案:

function A() {
    //核心代码
    (function(){
        for(var i = 0; i<3; i++) {
            console.log(i);
        }
    })()

    // 现在,作用域外无法访问到i了
    console.log(i)//underfined
}
A();

文章指出

注意看核心代码部分,我们用刚刚讲到的匿名自执行函数在内部形成了一个闭包,这个闭包在哪呢?一直强调,闭包的本质是函数,其实在这里闭包就是那个匿名函数

我觉得这好像跟闭包没什么关系? 闭包的定义是:

《JavaScript高级程序设计》这样描述:

闭包是指有权访问另一个函数作用域中的变量的函数;

《你不知道的JavaScript》这样描述:

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行

文章的代码根本没体现出关于闭包定义的情况。

为了更好的理解,我尝试把匿名函数改成 普通的函数调用:

function newA() {
    function newB() {
        for (var i = 0; i < 3; i++) {
            console.log(i)
        }
    }
    newB()
    console.log(i) //> Uncaught ReferenceError: i is not defined
}

难道这样能说newB函数是一个闭包吗?这只不过是把i变量的作用域缩小了,应该叫变量的私有化。 函数本身就有作用域,定义在内部的变量就是私有变量,所以并不需要闭包来模拟块作用域。 作者在文中指出闭包就是那个匿名函数的说法是错误的。

现在再看一个典型的闭包

function box(){
  var a = 10;
  function inner(){
    return a;
  }
  return inner;
}
var outer = box();
console.log(outer());//10

这个案例中用到的闭包其实是inner和inner被定义时的词法环境,这个闭包被return出来后被外部的outer引用,因此可以在box外部执行这个inner,inner能够读取到box内部的变量a。

再看 一个经典例题:

for(var i = 0;i<5;i++){
  setTimeout(function(){
    console.log(i);
  },100*i);
}

我们希望打印出来0,1,2,3,4,然而打印出来的是5个5,很尴尬。什么原因引起的这问题呢?这是因为setTimeout的回调函数并不是立即执行的而是要等到循环结束才开始计时和执行(在此对运行机制不伸展)。 要说明的一点是js中函数在执行前都只对变量保持引用,并不会真正获取和保存变量的值。 所以等循环结束后i的值是已经是5了,因此执行定时器的回调函数会打印出5个5。

现在给出解决方案:

for(var i = 0;i<5;i++){
  (function(i){
    setTimeout(function(){
      console.log(i);
    },100*i);
  })(i);
}

这个for循环中的闭包怎么理解以及自执行匿名函数的作用:

  1. 这个for循环其实是在执行定时器的回调函数时才真正的产生了闭包,这些回调函数的执行环境是window,类似刚才例子中的引用inner的全局outer的执行环境,匿名函数则相当于刚才例子中的box函数。

  2. 而自执行的匿名函数的作用也很简单:就是每一次循环创建一个私有词法环境,执行时把当前的循环的i传入,保存在这个词法环境中,这个i就类似上面box函数中var声明的局部变量a。

  3. 刚才有说到函数在被执行前都只是保存对变量的引用,自执行的匿名函数正是因为执行了,所以能够获取当前的变量i的值。因此定时器的回调函数在执行时引用的i就已经确定了具体的值。

我认为这两个概念之间的混淆来自于使用术语“闭包”,其中作者已经说过“下面的代码创建一个闭包”,然后给出了一个恰好使用匿名函数的例子。 在这种情况下,闭包机制通常是使特定代码段按预期工作的重要因素,而使用匿名函数而不是命名函数恰好是编码它的便捷方式。 阅读这些例子并且第一次看到“闭包”的人然后误解了这个术语。

参考: www.jianshu.com/p/0a3150afb… caibaojian.com/javascript-…