做用域鏈、變量對象、閉包

 變量對象:
 
1.變量對象(variable object) 是與執行上下文相關的 數據做用域(scope of data) 。
它是與上下文關聯的特殊對象,用於存儲被定義在上下文中的 變量(variables) 、 函數聲明(function declarations) 和 函數的形參 。
 
2.就像咱們所說的, VO就是執行上下文的屬性(property):
activeExecutionContext = {
VO: {
// 上下文數據(var, FD, function arguments)
}
};
 
3.在函數執行上下文中,VO是不能直接訪問的,此時由活動對象(activation object,縮寫爲AO)扮演VO的角色。
 
4.活動對象是在進入函數上下文時刻被建立的,它經過函數的arguments屬性初始化。arguments屬性的值是Arguments對象:
AO = {
arguments: <ArgO>
};
 
****5.執行上下文的代碼被分紅兩個基本的階段來處理:
  1.     進入執行上下文:這個階段AO/VO已經擁有了屬性,不過並非全部的屬性都有值,大部分屬性的值仍是系統默認的初始值undefined
  2.     執行代碼:這個階段AO/VO屬性值被修改
 
6.一般,各種文章和JavaScript相關的書籍都聲稱:「無論是使用var關鍵字(在全局上下文)仍是不使用var關鍵字(在任何地方),均可以聲明一個變量」。請記住,這是錯誤的概念:
任什麼時候候,變量只能經過使用var關鍵字才能聲明。
 
7.填充VO的優先級順序是: 函數的形參 -> 函數申明 -> 變量申明。
 
 
 做用域鏈:
 
1.做用域鏈正是內部上下文全部變量對象(包括父變量對象)的列表。此鏈用來變量查詢。
var x = 10;
 
function foo() {
var y = 20;
function bar() {
alert(x + y);
}
return bar;
}
 
foo()(); // 30
上面的例子中,「bar」上下文的做用域鏈包括AO(bar)、AO(foo)和VO(global)。
 
2.函數上下文的做用域鏈在函數調用時建立的,包含活動對象和這個函數內部的[[scope]]屬性。下面咱們將更詳細的討論一個函數的[[scope]]屬性。
在上下文中示意以下:
activeExecutionContext = {
VO: {...}, // or AO
this: thisValue,
Scope: [ // Scope chain
// 全部變量對象的列表
// for identifiers lookup
]
};
其scope定義以下:
Scope = AO + [[Scope]]
<上面代碼的意思是:活動對象是做用域數組的第一個對象,即添加到做用域鏈的前端。>
 
3.[[scope]]特色:
•[[scope]]是全部父變量對象的層級鏈,處於當前函數上下文之上,在函數建立時存於其中。
•注意這重要的一點--[[scope]]在函數建立時被存儲--靜態(不變的),永遠永遠,直至函數銷燬。即:函數能夠永不調用,但[[scope]]屬性已經寫入,並存儲在函數對象中。
•另一個須要考慮的是--與做用域鏈對比,[[scope]]是函數的一個屬性而不是上下文。
 
4.若是一個屬性在對象中沒有直接找到,查詢將在原型鏈中繼續。即常說的二維鏈查找。
活動對象沒有原型,咱們能夠在下面的例子中看到:
function foo() {
var x = 20;
 
function bar() {
alert(x);
}
bar();
}
Object.prototype.x = 10;
foo(); // 20
 
 
3、閉包:
 
1.閉包是代碼塊和建立該代碼塊的上下文中數據的結合。
閉包是一系列代碼塊(在ECMAScript中是函數),而且靜態保存全部父級的做用域。
閉包就是可以讀取上層上下文內部變量的函數。
 
2.在ECMAScript中,全部的函數都是閉包,由於它們都是在建立的時候就保存了上層上下文的做用域鏈(除開異常的狀況) (無論這個函數後續是否會激活 —— [[Scope]]在函數建立的時候就有了)
 
3.在ECMAScript中,同一個父上下文中建立的閉包是共用一個[[Scope]]屬性的。也就是說,某個閉包對其中[[Scope]]的變量作修改會影響到其餘閉包對其變量的讀取
這就是說:全部的內部函數都共享同一個父做用域
var data = [];
 
for (var k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
 
data[0](); // 3, 而不是0
data[1](); // 3, 而不是1
data[2](); // 3, 而不是2
上述例子就證實了 —— 同一個上下文中建立的閉包是共用一個[[Scope]]屬性的。所以上層上下文中的變量「k」是能夠很容易就被改變的。
activeContext.Scope = [
... // 其它變量對象
{data: [...], k: 3} // 活動對象
];
 
data[0].[[Scope]] === Scope;
data[1].[[Scope]] === Scope;
data[2].[[Scope]] === Scope;
這樣一來,在函數激活的時候,最終使用到的k就已經變成了3了。以下所示,建立一個閉包就能夠解決這個問題了:
var data = [];
 
for (var k = 0; k < 3; k++) {
data[k] = (function _helper(x) {
return function () {
alert(x);
};
})(k); // 傳入"k"值
}
 
// 如今結果是正確的了
data[0](); // 0
data[1](); // 1
data[2](); // 2
函數「_helper」建立出來以後,經過傳入參數「k」激活。其返回值也是個函數,該函數保存在對應的數組元素中。這種技術產生了以下效果: 在函數激活時,每次「_helper」都會建立一個新的變量對象,其中含有參數「x」,「x」的值就是傳遞進來的「k」的值。這樣一來,返回的函數的[[Scope]]就成了以下所示:
data[0].[[Scope]] === [
... // 其它變量對象
父級上下文中的活動對象AO: {data: [...], k: 3},
_helper上下文中的活動對象AO: {x: 0}
];
 
data[1].[[Scope]] === [
... // 其它變量對象
父級上下文中的活動對象AO: {data: [...], k: 3},
_helper上下文中的活動對象AO: {x: 1}
];
 
data[2].[[Scope]] === [
... // 其它變量對象
父級上下文中的活動對象AO: {data: [...], k: 3},
_helper上下文中的活動對象AO: {x: 2}
];
咱們看到,這時函數的[[Scope]]屬性就有了真正想要的值了,爲了達到這樣的目的,咱們不得不在[[Scope]]中建立額外的變量對象。
相關文章
相關標籤/搜索