JS(8)——闭包(closure)

1. 闭包的概念

官方的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。简单来说,闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成定义在一个函数内部的函数。 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

要理解闭包,首先必须理解Javascript特殊的变量作用域。变量的作用域两种:全局变量和局部变量。Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量 ,例如:

var n=999;  
function f1(){  
     alert(n);  
} 
f1(); // 999  

另一方面,在函数外部自然无法读取函数内的局部变量。例如:

function f1(){  
     var n=999;  
}  
alert(n); // error  

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,实际上声明了一个全局变量,即:

function f1(){  
     n=999;  
} 
f1();  
alert(n); // 999  

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。例如:

function f1(){  
    n=999;  
  function f2(){  
      alert(n); // 999  
  }
 }  

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,就可以在f1外部读取它的内部变量了。例如:

 function f1(){  
      n=999;  
      function f2(){  
          alert(n);  
    }  

    return f2;  
}  

var result=f1();  
result(); // 999 

 上面的f2函数,就是闭包。闭包其实就是定义在一个函数内部的函数(因为是子函数所以能够读取所在父函数的内部变量)。

注意:由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。


2.  闭包的用途

 1可以读取函数内部的变量,保护函数内的变量安全。

 2、在内存中维持一个变量。


3. 使用闭包的注意点 

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;
}

这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置0 的函数返回0,位置1 的函数返回1,以此类推。但实际上,每个函数都返回10。因为每个函数的作用域链中都保存着createFunctions() 函数的活动对象,

所以它们引用的都是同一个变量i 。当createFunctions()函数返回后,变量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 num;
                };
        }(i);
    }

    return result;
}

在重写了前面的createFunctions()函数后,每个函数就会返回各自不同的索引值了。在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量i。由于函数参数是按值传递的,所以就会将变量i 的当前值复制给参数num。而在这个匿名函数内部,又创建并返回了一个访问num 的闭包。这样一来,result 数组中的每个函数都有自己num 变量的一个副本,因此就可以返回各自不同的数值了。每个函数在被调用时都会自动取得两个特殊变量:this arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。不过,把外部作用域中的this 对象保存在一个闭包能够访问到的

变量里,就可以让闭包访问该对象了,例如:

var name = "The Window";

var object = {
    name : "My Object",

    getNameFunc : function(){
        var that = this;

        return function(){
            return that.name;
        };
    }
};

alert(object.getNameFunc()()); //"My Object"

代码中突出的行展示了这个例子与前一个例子之间的不同之处。在定义匿名函数之前,我们把this对象赋值给了一个名叫that 的变量。而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声名的一个变量。即使在函数返回之后,that 也仍然引用着object,所以调用object.getNameFunc()()就返回了"My Object"

arguments也存在同样的问题。如果想访问作用域中的arguments 对象,必须将对该对象的引用保存到另一个闭包能够访问的变量中。

注意:在几种特殊情况下,this 的值可能会意外地改变。比如,下面的代码是修改前面例子的结果

var name = "The Window";

var object = {
    name : "My Object",
    getName: function(){
        return this.name;
    }
};

这里的getName()方法只简单地返回this.name 的值。以下是几种调用object.getName()的方式以及各自的结果。

object.getName(); //"My Object"

(object.getName)(); //"My Object"

(object.getName = object.getName)(); //"The Window",在非严格模式下

第一行代码跟平常一样调用了object.getName(),返回的是"My Object",因为this.name就是object.name。第二行代码在调用这个方法前先给它加上了括号。虽然加上括号之后,就好像只是在引用一个函数,但this 的值得到了维持,因为object.getName (object.getName)的定义是相同的。第三行代码先执行了一条赋值语句,然后再调用赋值后的结果。因为这个赋值表达式的值是函数本身,所以this 的值不能得到维持,结果就返回了"The Window"

猜你喜欢

转载自blog.csdn.net/u013789656/article/details/80942752
今日推荐