JavaScript:闭包
闭包
闭包:指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式就是在一个函数内部创建另一个函数:
function createComparisonFunction(propertyName) {
return function (obj1, obj2) {
// 访问了外部函数中的变量
var value1 = obj1[propertyName];
var value2 = obj2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
上面的obj1、obj2访问了外部函数的变量propertyName。之所以能访问到这个变量,是因为内部函数的作用域中包含createComparisonFunction()的作用域。
当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。
在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。
function compare(value1, value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
上面先调用了compare()函数,然后又在全局作用域调用了它,当调用compare()时,会创建一个包含arguments、value1和value2的活动对象。全局执行环境的变量对象在compare()执行环境的作用域链中则处于第二位。
compare()函数执行时的作用域链:
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。因此createComparisonFunction()函数内部定义的匿名内部函数的作用域链中,实际上将会包含外部函数createComparisonFunction()的活动对象。
var compare = createComparisonFunction("name");
var result = compare({name: "Nicholas"}, {name: "Greg"});
下图展示了代码执行时,包含函数与内部匿名函数的作用域链。
匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction()中定义的所有变量。另外,在createComparisonFunction()执行完,它的活动对象不会被销毁,从图中可以看出,匿名函数还引用了这个活动对象。当createComparisonFunction()函数返回时,其执行环境的作用域链会被销毁,但它的活动对象仍会留在内存中;匿名函数被销毁后,createComparisonFunction()的活动对象才会销毁。
// 创建函数
var compareNames = createComparisonFunction("name");
// 调用函数
var result = compareNames({name: "Nicholas"}, {name: "Greg"});
// 解除对匿名函数的引用(释放内存)
compareNames = null;
首先,创建的比较函数被保存在compareNames,将compareNames设置为null,解除了该函数的引用,就可以进行清除了。
闭包与变量
闭包保存了整个变量对象,但只能取得包含函数中任何变量的最后一个值。
示例:
function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
return result;
}
上面的例子中看似每个函数都返回自己的索引值,实际上,每个函数都返回10。因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。
通过创建另一个匿名函数强制让闭包的行为符合预期,如下:
function createFunctions() {
for (var i = 0; i < 10; i++) {
return [i] = function (num) {
return function () {
return num;
};
}(i);
}
return result;
}
this对象
this对象在运行时是基于函数的执行环境进行绑定的,在全局函数中,this等于window;当某个对象的方法调用时,this等于那个对象。但是,匿名函数的执行环境具有全局性,其this对象指向window。
看一个例子:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()()); // "The Window"
上面的例子中首先创建了一个全局变量name,又创建了一个包含name属性的对象。这个对象包含一个方法:getNameFunc(),它返回一个匿名函数,而匿名函数又返回this.name。由于getNameFunc()返回一个函数,因此调用object.getNameFunc()()就会调用它返回的函数,结果为:this.name。然而,返回的字符串是"The Window"。
为什么匿名函数没有取得其包含作用域的this对象呢?
每个函数在函数在被调用时都会获取两个变量: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()()); // "The Window"