闭包
看如下示例:
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
例子中,突出的那两行代码是内部函数(一个匿名函数)中的代码,这两行代码访问了外部函数中的变量 propertyName 。即使这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可以访问变量 propertyName 。之所以还能够访问这个变量,因为内部函数的作用域链中包含createComparisonFunction() 的作用域。
在匿名函数从 createComparisonFunction() 中被返回后,它的作用域链被初始化为包含createComparisonFunction() 函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction() 中定义的所有变量。 更为重要的是, createComparisonFunction()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当 createComparisonFunction() 函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中;直到匿名函数被销毁后, createComparisonFunction() 的活动对象才会被销毁。
例如:
//创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });//1
调用 compareNames() 的过程中产生的作用域链之间的关系如下图所示:
闭包与变量
作用域链的这种配置机制引出了一个值得注意的副作用, 即闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。
示例如下:
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,但实际上,每个函数都返回 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;
}
例子中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数 num ,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量 i 。由于函数参数是按值传递的,所以就会将变量 i 的当前值复制给参数 num 。而在这个匿名函数内部,又创建并返回了一个访问 num 的闭包。这样一来, result 数组中的每个函数都有自己num 变量的一个副本,因此就可以返回各自不同的数值了。
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 和 arguments 。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
模仿块级作用域
JavaScript 没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。而且JavaScript从来不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见,不过,它会执行后续声明中的变量初始化。
不过匿名函数可以用来模仿块级作用域并避免这个问题:
(function(){
//这里是块级作用域
})();
将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。
无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); //导致一个错误!
}
在这个重写后的 outputNumbers() 函数中,我们在 for 循环外部插入了一个私有作用域。在匿名函数中定义的任何变量, 都会在执行结束时被销毁。 因此, 变量 i 只能在循环中使用, 使用后即被销毁。而在私有作用域中能够访问变量 count ,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。