闭包是指有权访问另一个函数作用域中变量的函数。 创建闭包的常见方式,就是在一个函数内部创建另一个函数。本质上讲,闭包就是讲函数内部和函数外部连接起来的一座桥梁。
function a(){ var i = 0; function b(){ alert(++i); } return b;}var c = a();c(); //1
在函数a 中嵌套了函数b,并将函数b返回。
在执行完 var c = a() 后,变量c实际上指向了函数b,再执行c()后就会弹出一个窗口显示i的值。
当函数a的内部函数b被函数a外的一个变量引用时,就创建了一个闭包。
闭包主要涉及到js的几个其他的特性: 作用域链,垃圾(内存)回收机制,函数嵌套
function compare(value1, value2){ if(value1 > value2){ return 1; }else if(value1 < value2){ return -1; }else{ return 0; }}var result = compare(5,10);
1. 作用域链:
作用域链是在函数定义时创建的,当函数需要查询一个变量的值的时候,js解析器会去作用域链中查找。在上述例子中,查找i时,会先在函数b中找,找到则返回,没找到继续在上一级的链即函数a中查找,如果还没找到,再到全局链中找,如果全局链中也没有,则返回undefined。
在创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。 当调用函数时,会为函数创建一个执行环境,然后通过复制函数[[Scope]]属性中的对象构建起执行环境的作用域链。
在上面的例子中,对于compare()函数的执行环境而言,其作用域中包含两个变量对象: 本地活动对象和全局变量对象。作用域链本质上是一个指向变量对象的指针表,它只引用但不实际包含变量对象。
在函数中访问一个变量时,会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。但闭包的情况又有所不同。
2.垃圾回收机制:
一个函数在执行开始的时候,会给其中定义的变量划分内存空间,用来保存这些变量。等函数执行完毕后,这些变量的内存就会被回收。下次再执行时,又会重新分配。
如果一个函数的内部嵌套了另外的一个函数,内部函数有可能在外部被调用到,并且这个内部函数使用了外部函数的某些变量。
这种情况下,js解释器在遇到函数定义的时候,会自动把函数和它可能用到的变量(包括本地变量、父级和祖父级函数的变量)一起保存下来,也就是构建一个闭包。
这些变量将不会被内存回收器回收。只有当内部的函数不可能被调用以后,才会销毁这个闭包。
3.闭包与变量:
闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。
var result = new Array();function createFunctions(){ for(var i=0; i<10; i++){ result[i] = function(){ return i; } } return result;}createFunctions();alert(result[0]()); //10alert(result[9]()); //10
这个函数会返回一个函数数组,表面上看,似乎每个函数都应返回自己的索引值,但实际中,每个函数都返回10。
因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以他们引用的都是同一个变量 i。当createFunctions()函数返回后,变量i的值时10,
此时,每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部 i 的值都是10。
我们可以创建另一个匿名函数强制让闭包的行为符合预期。
var result = new Array();function createFunctions(){ for(var i=0; i<10; i++){ result[i] = function(num){ return function(){ return num; } }(i); } return result;}createFunctions();alert(result[0]()); //0alert(result[9]()); //9
在上述代码中,没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给数组。这里的匿名函数有一个参数num,也就是最终函数要返回的值。在调用匿名函数时,我们传入了参数 i 。由于函数参数是按值传递的,所以就会将变量 i的值复制给参数num。而在这个匿名函数内部,又创建并返回了num的闭包。这样一来,result数组的每个函数都有自己 num变量的一个副本,因此就可以返回各自不同的数值了。
4.关于this对象:
在闭包中使用this对象也会导致一些问题。this对象是在运行时基于函数的执行环境绑定的: 在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于当前对象。 不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。但有时候,由于编写闭包的方式不同,这一点可能不会那么明显:
var name = "The Window";var object = { name: "My Object", getNameFunc: function(){ return function(){ this.name; }; }};alert(object.getNameFunc()()); //The Window
以上代码先创建了一个全局变量name,又创建了一个包含name属性的对象。这个对象还包含一个方法 -- getNameFunc(), 它返回一个匿名函数,而匿名函数又返回this.name。由于getNameFunc()返回一个函数,因此调用object.getNameFunc()()就会立即调用它返回的函数,结果就是返回一个字符串。然而这个结果是全局name变量的值"The Window"。
每个函数在被调用时,其活动对象都会自动取得两个特殊变量: 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"。