AnthonyZero's Bolg

闭包

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

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。所以在函数外部自然无法读取函数内的局部变量, 但是闭包的情况不同,它可以在外部访问函数内部变量。如例所示:

function myFunction() {
  var name = 'Bob';
  function displayName() {
    console.log(name);
  }
  return displayName;
}

var myFunc = myFunction();
myFunc();

myFunction()返回了一个闭包:这个闭包由displayName()函数和闭包创建时存在的name变量组成.由于 displayName()持有了name的引用,myFunc持有了displayName()的引用,因此myFunc调用时,name还是可以被访问

由于闭包会携带包含函数的作用域(闭包会将包含它的函数即父函数的活动对象添加到它的作用域链中,这就是为什么闭包可以访问包含函数中的变量的原因),因此会比其它函数占用更多的内存(因为包含闭包的父|外部函数的变量被保存在内存中)。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包可以在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包与变量

闭包只能取得包含函数中任何变量的最后(最新的值)一个值(这是因为可以在父函数外部通过闭包修改父函数内部变量的值),这是因为闭包所保存的是整个变量对象,而不是某个特殊的变量。

function createFunctions(){
    var result = new Array();
    for(var i=0; i<10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}

这个函数返回一个函数数组,每个函数都返回10。这是因为每个函数的作用域链中都保存着createFunctions()的活动对象,所以它们都引用的都是同一个变量i。当循环执行完,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以每个函数内部i的值都是10。

对于上面的情况,如果我们改变代码如下:

function createFunctions(){
    var result = new Array();
    for(var i=0; i<10; i++){
        result[i] = function(num){
            return function(){
                return i;
            };
        }(i);
    }
    return result;
}

此时函数数组内每个函数内部i的值就是从0到9了

闭包中的this对象

var name = "The Window";
var obj = {
  name: "My Object",

  getName: function(){
                          //外部函数的变量等
    return function(){  //this和arguments
      return this.name;   
    };
  }
};
console.log(obj.getName()());  // The Window

每个函数在被调用时都会自动获取两个特殊变量:this和arguments,内部函数在搜索这两个变量时,只会搜索到其活动对象为止。obj.getName()()实际上是在全局作用域中调用了匿名函数,this指向了window。

var name = "The Window";
var obj = {
  name: "My Object",

  getName: function(){
    var that = this;
    return function(){   //闭包
      return that.name;
    };
  }
};
console.log(obj.getName()());  // My Object

让外部作用域中(包含函数的作用域)的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了(如上就是name变量)

闭包用途其一 模仿块级作用域

(function() {
    // 块级作用域(通常称为私有作用域)
}) (); //定义并立即调用了一个匿名函数。既可以执行其中的代码,又不会在内存中留下对该函数的引用


function outputNumbers(count){
    (function(){
        for(var i = 0; i < count; i++){
          console.log(i);
        }
    })();
    console.log(i); //导致一个错误!i没有定义
}

变量i只能在循环中使用,使用后即被销毁。而在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。

  • 好处:过多的全局变量和函数很容易导致命名冲突,而通过创建私有作用域,每个人既可以使用自己的变量,又不用担心搞乱全局作用域。

闭包用途其二 创建私有变量

function MyObject(){
    // 共有变量 可在外部直接访问
    this.number = 50;

    // 私有变量
    var privateVariable = 10;

    // 私有函数
    function privateFunction(){
      return false;
    }

    // 特权方法,调用私有方法、函数
    this.publicMethod = function(){
      privateVariable++;
      return privateFunction();
    }
}

我们把有权访问私有变量、私有函数的共有方法称为特权方法。特权方法是作为闭包有权访问在构造函数中定义的所有变量和函数。