# 闭包

每一个函数都是闭包。

内层函数可以访问外层函数定义的变量(this,arguments对象除外)。

当外层函数执行完,被内层函数引用的变量不会被销毁,仍然可以访问。

缺点:占用内存。

应用:模仿块级作用域,私有变量, 模块模式

  1. 模仿块级作用域

JavaScript 没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含 函数中而非语句中创建的

(function(){ 
 //这里是块级作用域
})();

一些库都是这种写法,从而限制向全局作用域中添加过多的变量和函数

  1. 私有变量

任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。 私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

function MyObject(){ 
 //私有变量和私有函数
 var privateVariable = 10; 
 function privateFunction(){ 
  return false; 
 } 
 //特权方法
 this.publicMethod = function (){ 
  privateVariable++; 
  return privateFunction(); 
 }; 
}
  1. 模块模式
var singleton = function(){ 
 
 //私有变量和私有函数
 var privateVariable = 10; 
 
 function privateFunction(){
  return false; 
 } 

 //特权/公有方法和属性
 return { 
  publicProperty: true, 
  publicMethod : function(){ 
    privateVariable++; 
    return privateFunction(); 
  } 
 }; 
}();

# 必刷题

接下来,看这道刷题必刷,面试必考的闭包题:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

答案是都是 3,让我们分析一下原因:

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

当执行 data[0] 函数的时候,data[0] 函数的作用域链为:

data[0]Context = {
    Scope: [AO, globalContext.VO]
}

data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i 为 3,所以打印的结果就是 3。

data[1] 和 data[2] 是一样的道理。

所以让我们改成闭包看看:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}

data[0]();
data[1]();
data[2]();

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

跟没改之前一模一样。

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:

data[0]Context = {
    Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

匿名函数执行上下文的 AO 为:

匿名函数Context = {
    AO: {
        arguments: {
            0: 0,
            length: 1
        },
        i: 0
    }
}

data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是 0。

data[1] 和 data[2] 是一样的道理。

# 如果使上面的例子,打印正确的结果

  1. 使用let
var data = [];

for (let i = 0; i < 3; i++) {
  data[i] = function(){
    console.log(i);
   };
}

data[0]();
data[1]();
data[2]();