免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
深入理解JavaScript系列(14):作用域鏈(Scope Chain)

前言

在第12章關(guān)于變量對象的描述中,我們已經(jīng)知道一個執(zhí)行上下文 的數(shù)據(jù)(變量、函數(shù)聲明和函數(shù)的形參)作為屬性存儲在變量對象中。

同時我們也知道變量對象在每次進入上下文時創(chuàng)建,并填入初始值,值的更新出現(xiàn)在代碼執(zhí)行階段。

這一章專門討論與執(zhí)行上下文直接相關(guān)的更多細節(jié),這次我們將提及一個議題——作用域鏈。

英文原文:http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/
中文參考:http://www.denisdeng.com/?p=908
本文絕大部分內(nèi)容來自上述地址,僅做少許修改,感謝作者

定義

如果要簡要的描述并展示其重點,那么作用域鏈大多數(shù)與內(nèi)部函數(shù)相關(guān)。

我們知道,ECMAScript 允許創(chuàng)建內(nèi)部函數(shù),我們甚至能從父函數(shù)中返回這些函數(shù)。

var x = 10;

function foo() {
var y = 20;
function bar() {
alert(x + y);
}
return bar;
}

foo()(); // 30

這樣,很明顯每個上下文擁有自己的變量對象:對于全局上下文,它是全局對象自身;對于函數(shù),它是活動對象。

作用域鏈正是內(nèi)部上下文所有變量對象(包括父變量對象)的列表。此鏈用來變量查詢。即在上面的例子中,“bar”上下文的作用域鏈包括AO(bar)、AO(foo)和VO(global)。

但是,讓我們仔細研究這個問題。

讓我們從定義開始,并進深一步的討論示例。

作用域鏈與一個執(zhí)行上下文相關(guān),變量對象的鏈用于在標識符解析中變量查找。

函數(shù)上下文的作用域鏈在函數(shù)調(diào)用時創(chuàng)建的,包含活動對象和這個函數(shù)內(nèi)部的[[scope]]屬性。下面我們將更詳細的討論一個函數(shù)的[[scope]]屬性。

在上下文中示意如下:

activeExecutionContext = {
VO: {...}, // or AO
this: thisValue,
Scope: [ // Scope chain
// 所有變量對象的列表
// for identifiers lookup
]
};

其scope定義如下:

Scope = AO + [[Scope]]

這種聯(lián)合和標識符解析過程,我們將在下面討論,這與函數(shù)的生命周期相關(guān)。

函數(shù)的生命周期

函數(shù)的的生命周期分為創(chuàng)建和激活階段(調(diào)用時),讓我們詳細研究它。

函數(shù)創(chuàng)建

眾所周知,在進入上下文時函數(shù)聲明放到變量/活動(VO/AO)對象中。讓我們看看在全局上下文中的變量和函數(shù)聲明(這里變量對象是全局對象自身,我們還記得,是吧?)

var x = 10;

function foo() {
var y = 20;
alert(x + y);
}

foo(); // 30

在函數(shù)激活時,我們得到正確的(預期的)結(jié)果--30。但是,有一個很重要的特點。

此前,我們僅僅談到有關(guān)當前上下文的變量對象。這里,我們看到變量“y”在函數(shù)“foo”中定義(意味著它在foo上下文的AO中),但是變量“x”并未在“foo”上下文中定義,相應地,它也不會添加到“foo”的AO中。乍一看,變量“x”相對于函數(shù)“foo”根本就不存在;但正如我們在下面看到的——也僅僅是“一瞥”,我們發(fā)現(xiàn),“foo”上下文的活動對象中僅包含一個屬性--“y”。

fooContext.AO = {
y: undefined // undefined – 進入上下文的時候是20 – at activation
};

函數(shù)“foo”如何訪問到變量“x”?理論上函數(shù)應該能訪問一個更高一層上下文的變量對象。實際上它正是這樣,這種機制是通過函數(shù)內(nèi)部的[[scope]]屬性來實現(xiàn)的。

[[scope]]是所有父變量對象的層級鏈,處于當前函數(shù)上下文之上,在函數(shù)創(chuàng)建時存于其中。

注意這重要的一點--[[scope]]在函數(shù)創(chuàng)建時被存儲--靜態(tài)(不變的),永遠永遠,直至函數(shù)銷毀。即:函數(shù)可以永不調(diào)用,但[[scope]]屬性已經(jīng)寫入,并存儲在函數(shù)對象中。

另外一個需要考慮的是--與作用域鏈對比,[[scope]]是函數(shù)的一個屬性而不是上下文。考慮到上面的例子,函數(shù)“foo”的[[scope]]如下:

foo.[[Scope]] = [
globalContext.VO // === Global
];

舉例來說,我們用通常的ECMAScript 數(shù)組展現(xiàn)作用域和[[scope]]。

繼續(xù),我們知道在函數(shù)調(diào)用時進入上下文,這時候活動對象被創(chuàng)建,this和作用域(作用域鏈)被確定。讓我們詳細考慮這一時刻。

函數(shù)激活

正如在定義中說到的,進入上下文創(chuàng)建AO/VO之后,上下文的Scope屬性(變量查找的一個作用域鏈)作如下定義:

Scope = AO|VO + [[Scope]]

上面代碼的意思是:活動對象是作用域數(shù)組的第一個對象,即添加到作用域的前端。

Scope = [AO].concat([[Scope]]);

這個特點對于標示符解析的處理來說很重要。

標示符解析是一個處理過程,用來確定一個變量(或函數(shù)聲明)屬于哪個變量對象。

這個算法的返回值中,我們總有一個引用類型,它的base組件是相應的變量對象(或若未找到則為null),屬性名組件是向上查找的標示符的名稱。引用類型的詳細信息在第13章.this中已討論。

標識符解析過程包含與變量名對應屬性的查找,即作用域中變量對象的連續(xù)查找,從最深的上下文開始,繞過作用域鏈直到最上層。

這樣一來,在向上查找中,一個上下文中的局部變量較之于父作用域的變量擁有較高的優(yōu)先級。萬一兩個變量有相同的名稱但來自不同的作用域,那么第一個被發(fā)現(xiàn)的是在最深作用域中。

我們用一個稍微復雜的例子描述上面講到的這些。

var x = 10;

function foo() {
var y = 20;

function bar() {
var z = 30;
alert(x + y + z);
}

bar();
}

foo(); // 60

對此,我們有如下的變量/活動對象,函數(shù)的的[[scope]]屬性以及上下文的作用域鏈:

全局上下文的變量對象是:

globalContext.VO === Global = {
x: 10
foo: <reference to function>
};

在“foo”創(chuàng)建時,“foo”的[[scope]]屬性是:

foo.[[Scope]] = [
globalContext.VO
];

在“foo”激活時(進入上下文),“foo”上下文的活動對象是:

fooContext.AO = {
y: 20,
bar: <reference to function>
};

“foo”上下文的作用域鏈為:

fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:

fooContext.Scope = [
fooContext.AO,
globalContext.VO
];

內(nèi)部函數(shù)“bar”創(chuàng)建時,其[[scope]]為:

bar.[[Scope]] = [
fooContext.AO,
globalContext.VO
];

在“bar”激活時,“bar”上下文的活動對象為:

barContext.AO = {
z: 30
};

“bar”上下文的作用域鏈為:

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:

barContext.Scope = [
barContext.AO,
fooContext.AO,
globalContext.VO
];

對“x”、“y”、“z”的標識符解析如下:

- "x"
-- barContext.AO // not found
-- fooContext.AO // not found
-- globalContext.VO // found - 10

- "y"
-- barContext.AO // not found
-- fooContext.AO // found - 20

- "z"
-- barContext.AO // found - 30

作用域特征

讓我們看看與作用域鏈和函數(shù)[[scope]]屬性相關(guān)的一些重要特征。

閉包

在ECMAScript中,閉包與函數(shù)的[[scope]]直接相關(guān),正如我們提到的那樣,[[scope]]在函數(shù)創(chuàng)建時被存儲,與函數(shù)共存亡。實際上,閉包是函數(shù)代碼和其[[scope]]的結(jié)合。因此,作為其對象之一,[[Scope]]包括在函數(shù)內(nèi)創(chuàng)建的詞法作用域(父變量對象)。當函數(shù)進一步激活時,在變量對象的這個詞法鏈(靜態(tài)的存儲于創(chuàng)建時)中,來自較高作用域的變量將被搜尋。

例如:

var x = 10;

function foo() {
alert(x);
}

(function () {
var x = 20;
foo(); // 10, but not 20
})();

我們再次看到,在標識符解析過程中,使用函數(shù)創(chuàng)建時定義的詞法作用域--變量解析為10,而不是30。此外,這個例子也清晰的表明,一個函數(shù)(這個例子中為從函數(shù)“foo”返回的匿名函數(shù))的[[scope]]持續(xù)存在,即使是在函數(shù)創(chuàng)建的作用域已經(jīng)完成之后。

關(guān)于ECMAScript中閉包的理論和其執(zhí)行機制的更多細節(jié),閱讀16章閉包。

通過構(gòu)造函數(shù)創(chuàng)建的函數(shù)的[[scope]]

在上面的例子中,我們看到,在函數(shù)創(chuàng)建時獲得函數(shù)的[[scope]]屬性,通過該屬性訪問到所有父上下文的變量。但是,這個規(guī)則有一個重要的例外,它涉及到通過函數(shù)構(gòu)造函數(shù)創(chuàng)建的函數(shù)。

var x = 10;

function foo() {

var y = 20;

function barFD() { // 函數(shù)聲明
alert(x);
alert(y);
}

var barFE = function () { // 函數(shù)表達式
alert(x);
alert(y);
};

var barFn = Function('alert(x); alert(y);');

barFD(); // 10, 20
barFE(); // 10, 20
barFn(); // 10, "y" is not defined

}

foo();

我們看到,通過函數(shù)構(gòu)造函數(shù)(Function constructor)創(chuàng)建的函數(shù)“bar”,是不能訪問變量“y”的。但這并不意味著函數(shù)“barFn”沒有[[scope]]屬性(否則它不能訪問到變量“x”)。問題在于通過函構(gòu)造函數(shù)創(chuàng)建的函數(shù)的[[scope]]屬性總是唯一的全局對象??紤]到這一點,如通過這種函數(shù)創(chuàng)建除全局之外的最上層的上下文閉包是不可能的。

二維作用域鏈查找

在作用域鏈中查找最重要的一點是變量對象的屬性(如果有的話)須考慮其中--源于ECMAScript 的原型特性。如果一個屬性在對象中沒有直接找到,查詢將在原型鏈中繼續(xù)。即常說的二維鏈查找。(1)作用域鏈環(huán)節(jié);(2)每個作用域鏈--深入到原型鏈環(huán)節(jié)。如果在Object.prototype 中定義了屬性,我們能看到這種效果。

function foo() {
alert(x);
}

Object.prototype.x = 10;

foo(); // 10

活動對象沒有原型,我們可以在下面的例子中看到:

function foo() {

var x = 20;

function bar() {
alert(x);
}

bar();
}

Object.prototype.x = 10;

foo(); // 20

如果函數(shù)“bar”上下文的激活對象有一個原型,那么“x”將在Object.prototype 中被解析,因為它在AO中不被直接解析。但在上面的第一個例子中,在標識符解析中,我們到達全局對象(在一些執(zhí)行中并不全是這樣),它從Object.prototype繼承而來,響應地,“x”解析為10。

同樣的情況出現(xiàn)在一些版本的SpiderMokey 的命名函數(shù)表達式(縮寫為NFE)中,在那里特定的對象存儲從Object.prototype繼承而來的函數(shù)表達式的可選名稱,在Blackberry中的一些版本中,執(zhí)行時激活對象從Object.prototype繼承。但是,關(guān)于該特色的更多細節(jié)在第15章函數(shù)討論。

全局和eval上下文中的作用域鏈

這里不一定很有趣,但必須要提示一下。全局上下文的作用域鏈僅包含全局對象。代碼eval的上下文與當前的調(diào)用上下文(calling context)擁有同樣的作用域鏈。

globalContext.Scope = [
Global
];

evalContext.Scope === callingContext.Scope;

代碼執(zhí)行時對作用域鏈的影響

在ECMAScript 中,在代碼執(zhí)行階段有兩個聲明能修改作用域鏈。這就是with聲明和catch語句。它們添加到作用域鏈的最前端,對象須在這些聲明中出現(xiàn)的標識符中查找。如果發(fā)生其中的一個,作用域鏈簡要的作如下修改:

Scope = withObject|catchObject + AO|VO + [[Scope]]

在這個例子中添加對象,對象是它的參數(shù)(這樣,沒有前綴,這個對象的屬性變得可以訪問)。

var foo = {x: 10, y: 20};

with (foo) {
alert(x); // 10
alert(y); // 20
}

作用域鏈修改成這樣:

Scope = foo + AO|VO + [[Scope]]

我們再次看到,通過with語句,對象中標識符的解析添加到作用域鏈的最前端:

var x = 10, y = 10;

with ({x: 20}) {

var x = 30, y = 30;

alert(x); // 30
alert(y); // 30
}

alert(x); // 10
alert(y); // 30

在進入上下文時發(fā)生了什么?標識符“x”和“y”已被添加到變量對象中。此外,在代碼運行階段作如下修改:

  1. x = 10, y = 10;
  2. 對象{x:20}添加到作用域的前端;
  3. 在with內(nèi)部,遇到了var聲明,當然什么也沒創(chuàng)建,因為在進入上下文時,所有變量已被解析添加;
  4. 在第二步中,僅修改變量“x”,實際上對象中的“x”現(xiàn)在被解析,并添加到作用域鏈的最前端,“x”為20,變?yōu)?0;
  5. 同樣也有變量對象“y”的修改,被解析后其值也相應的由10變?yōu)?0;
  6. 此外,在with聲明完成后,它的特定對象從作用域鏈中移除(已改變的變量“x”--30也從那個對象中移除),即作用域鏈的結(jié)構(gòu)恢復到with得到加強以前的狀態(tài)。
  7. 在最后兩個alert中,當前變量對象的“x”保持同一,“y”的值現(xiàn)在等于30,在with聲明運行中已發(fā)生改變。

同樣,catch語句的異常參數(shù)變得可以訪問,它創(chuàng)建了只有一個屬性的新對象--異常參數(shù)名。圖示看起來像這樣:

try {
...
} catch (ex) {
alert(ex);
}

作用域鏈修改為:

var catchObject = {
ex: <exception object>
};

Scope = catchObject + AO|VO + [[Scope]]

在catch語句完成運行之后,作用域鏈恢復到以前的狀態(tài)。

結(jié)論

在這個階段,我們幾乎考慮了與執(zhí)行上下文相關(guān)的所有常用概念,以及與它們相關(guān)的細節(jié)。按照計劃--函數(shù)對象的詳細分析:函數(shù)類型(函數(shù)聲明,函數(shù)表達式)和閉包。順便說一下,在這篇文章中,閉包直接與[[scope]]屬性相關(guān),但是,關(guān)于它將在合適的篇章中討論。我很樂意在評論中回答你的問題。

其它參考

同步與推薦

本文已同步至目錄索引:深入理解JavaScript系列

深入理解JavaScript系列文章,包括了原創(chuàng),翻譯,轉(zhuǎn)載等各類型的文章,如果對你有用,請推薦支持一把,給大叔寫作的動力。

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
JavaScript作用域鏈其二:函數(shù)的生命周期
手把手教會你JavaScript引擎如何執(zhí)行JavaScript代碼
作用域鏈(Scope Chain)
js - 基礎(chǔ) 之 預編譯總結(jié)
前端基礎(chǔ)進階(三):變量對象詳解
JavaScript 預編譯與作用域
更多類似文章 >>
生活服務
分享 收藏 導長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服