每個函數對象在聲明或用表達式定義後都會被賦給一個特殊的內部屬性,這個屬性無法在代碼中訪問,但可以在瀏覽器調試中查看。例如在Chrome的調試界面在WatchExpressions中打印函數對象後,會看到一個<function scope>屬性:
這個<functionscope>屬性就是函數參考作用域鏈,是一個鏈表。頭結點"可能"是聲明或定義這個函數的函數的活動對象。所謂活動像是指一個函數執行時創建的其內部局部變量,參數等函數體內"一切"的一個集合。鏈表的下一節"可能"就是再外面那個函數的活動對象。作用域鏈的底部"總會"有一個Global對象,一般就是window對象。
前面有一些名詞打上了引號,因爲要對此補充說明。雖然函數式一個嵌套一個的,但最裏面的函數的作用域鏈並不一定包含外層函數所有的活動對象。只有這個函數內部出現了對外部某個函數活動對象內變量的訪問,纔會在它的作用域鏈裏添加這個外部函數的活動對象。而且,這個活動對象也並不包含對應的外部函數內部的所有變量,只包含內部函數訪問到的變量。但是,無論是否訪問Global對象的變量,Global對象是始終被包含在作用域鏈底部的。下面這個例子說明這一點:
打印func3函數時看到,作用域鏈裏只包含func1函數的活動對象,且只出現了v1變量。底部仍出現了Global對象。
<function scope>屬性總是包含該函數的外部函數創建的活動對象,當該函數被調用時,就會創建該函數本身的活動對象。這時,一個[[Scope]]屬性就會被創建,內容也是一個鏈表,只不過該函數本身的活動對象被插在鏈表的頂部,鏈表其餘部分就是<function scope>的內容。[[Scope]]就是函數調用作用域鏈,函數體內所有變量的訪問都是在這個[[Scope]]內查找的。這也是爲什麼前面把<function scope>稱爲參考作用域鏈的原因。注意,一個函數每次調用時都會創建一個新的活動對象。
但在外部函數內部定義的所有內部函數體內,若有引用外部函數變量,則引用到的變量是同一個變量,因爲所有內部函數的<function scope>作用域鏈裏都保留外部函數的同一個活動對象的引用,所以引用到這個活動對象裏的變量亦是同一個。要認識到,作用域鏈裏的一切都是指針。下面是一個例子:
funcGet和funcAdd函數的作用域鏈中都包含指向funcOuter函數的同一個活動對象的指針。理解這一點後也就理解了下面這個例子,這是個閉包的錯誤用法:
function User(properties) {
for (var i in properties) {
this[ "get" + i ] = function () {
return properties[i];
};
}
}
var user = new User({
name: "Bob",
age: 44
});
alert(user.getname()); //44
alert(user.getage()); //44
User這個構造函數將傳入的參數裏的屬性名增加前綴get,並作爲構造出的實例的屬性,返回參數裏對應的值。但兩個get函數都返回44,因爲這兩個get函數的<function scop>裏都引用到同一個User的活動對象,而在調用alert時這個活動對象裏的變量i的值總是age(for循環後的最終值)。要解決這一問題,可採用如下方法,實際上是在<funciton scope>裏增加了一環活動對象,而這一環的活動對象在每次循環創建時都是不同的:
function User(properties) {
for (var i in properties) {
(function() {
var j = i;
this[ "get" + j ] = function () {
return properties[j];
};
}).apply(this);
}
}
我們將循環體用一個匿名函數包裹,這樣一來,兩個get函數的參考作用域鏈裏分別包含兩個匿名函數不同的活動對象,這兩個活動對象裏的變量j一個是name,一個是age。這個解決方法有兩點需要留意,一個是var j=i;這一行,一開始我沒加這一行,也試過var i=i;,發現都不行,所以若要延長作用域鏈,得將變量改名。還有一點是apply(this),一開始我沒加,以爲這個匿名函數的執行環境是User構造函數創建的實例,實際上發現是window對象,
所以要注意自執行匿名函數的執行環境都是window。
下面是些閉包的用途:
靜態私有變量:
單例模式:
new Application()返回的對象隨不同,但訪問的資源都是components