只要所有外部函數的變量對象都存在,那麼從內部函數引用外部數據則沒有特別之處——我們只要遍歷作用域鏈表,查找所需變量。然而,如上文所提及,當一個上下文終止之後,其狀態與自身將會被 銷燬(destroyed) ,同時內部函數將會從外部函數中返回。此外,這個返回的函數之後可能會在其他的上下文中被激活,那麼如果一個之前被終止的含有一些自由變量的上下文又被激活將會怎樣?通常來說,解決這個問題的概念在ECMAScript中與作用域鏈直接相關,被稱爲 (詞法)閉包((lexical) closure)。
ECMAScript中,函數是第一類對象,意味着函數可以作爲參數傳遞給另一個函數,這個被傳遞的函數一般稱爲funargs(functional arguments),接收的函數被稱爲higher-order functions(高階函數),還有一類返回函數的函數被稱爲function valued函數。
閉包
一般意義上(與下面理論上相比)閉包產生的條件:
1. 函數可以被作爲返回值返回到上級函數或者可以被作爲參數傳給下級函數;
2. 該語言使用靜態作用域,普通函數在執行時使用的是該函數在創建時的作用域鏈。
function foo() {
var x = 10;
return function bar() {
console.log(x);
};
}
// "foo" returns also a function
// and this returned function uses
// free variable "x"
var returnedFunction = foo();
// global variable "x"
var x = 20;
// execution of the returned function
returnedFunction(); // 10, but not 20
作爲返回值的函數在創建時,自身的作用域鏈中會保存父函數的作用域鏈。如上面的例子:bar的作用域鏈中包含foo的作用域鏈,而bar的作用域鏈用於在之後調用時查詢變量。
// global "x"
var x = 10;
// global function
function foo() {
console.log(x);
}
(function (funArg) {
// local "x"
var x = 20;
// there is no ambiguity,
// because we use global "x",
// which was statically saved in
// [[Scope]] of the "foo" function,
// but not the "x" of the caller's scope,
// which activates the "funArg"
funArg(); // 10, but not 20
})(foo); // pass "down" foo as a "funarg"
funArg在調用時使用的是foo創建時的作用域鏈,所以x是10。
綜上,閉包可以定義爲:一個保存了所有父作用域的代碼塊,該代碼塊在執行時在這些父作用域中查詢變量。
由於ECMAScript中所有的函數在創建時都保存了作用域鏈,所以理論上說所有的函數都是閉包。
閉包中的數據共享:
幾個函數可能擁有相同的父級作用域,這種情況下變量是共享的,一個閉包中的變量改變,另外的也同樣改變。
function baz() {
var x = 1;
return {
foo: function foo() { return ++x; },
bar: function bar() { return --x; }
};
}
var closures = baz();
console.log(
closures.foo(), // 2
closures.bar() // 1
);
這就引發了前面一章(使用閉包和立即執行的方法來保存狀態,http://blog.csdn.net/ymjring/article/details/40889257)中提到的問題:
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2
解決方式:
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = (function (x) {
return function () {
alert(x);
};
})(k); // 將k當做參數傳遞進去
}
// 結果正確
data[0](); // 0
data[1](); // 1
data[2](); // 2