在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 作用域鏈(Scope Chain)
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫(xiě)高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對(duì)象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對(duì)象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒(méi)有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開(kāi)閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

作用域鏈(Scope Chain)

前言

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

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

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

定義

如果要簡(jiǎn)要的描述并展示其重點(diǎn),那么作用域鏈大多數(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

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

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

但是,讓我們仔細(xì)研究這個(gè)問(wèn)題。

讓我們從定義開(kāi)始,并進(jìn)深一步的討論示例。

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

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

在上下文中示意如下:

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

其 scope 定義如下:

Scope = AO + [[Scope]]

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

函數(shù)的生命周期

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

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

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

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

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

此前,我們僅僅談到有關(guān)當(dāng)前上下文的變量對(duì)象。這里,我們看到變量“y”在函數(shù)“foo”中定義(意味著它在 foo 上下文的 AO 中),但是變量“x”并未在“foo”上下文中定義,相應(yīng)地,它也不會(huì)添加到“foo”的 AO 中。乍一看,變量“x”相對(duì)于函數(shù)“foo”根本就不存在;但正如我們?cè)谙旅婵吹降摹矁H僅是“一瞥”,我們發(fā)現(xiàn),“foo”上下文的活動(dòng)對(duì)象中僅包含一個(gè)屬性--“y”。

fooContext.AO = {
  y: undefined // undefined – 進(jìn)入上下文的時(shí)候是20 – at activation
};

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

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

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

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

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

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

繼續(xù),我們知道在函數(shù)調(diào)用時(shí)進(jìn)入上下文,這時(shí)候活動(dòng)對(duì)象被創(chuàng)建,this 和作用域(作用域鏈)被確定。讓我們?cè)敿?xì)考慮這一時(shí)刻。

函數(shù)激活

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

Scope = AO|VO + [[Scope]]

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

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

這個(gè)特點(diǎn)對(duì)于標(biāo)示符解析的處理來(lái)說(shuō)很重要。

標(biāo)示符解析是一個(gè)處理過(guò)程,用來(lái)確定一個(gè)變量(或函數(shù)聲明)屬于哪個(gè)變量對(duì)象。

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

標(biāo)識(shí)符解析過(guò)程包含與變量名對(duì)應(yīng)屬性的查找,即作用域中變量對(duì)象的連續(xù)查找,從最深的上下文開(kāi)始,繞過(guò)作用域鏈直到最上層。

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

我們用一個(gè)稍微復(fù)雜的例子描述上面講到的這些。

var x = 10;
function foo() {
  var y = 20;
  function bar() {
    var z = 30;
    alert(x +  y + z);
  }
  bar();
}
foo(); // 60

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

全局上下文的變量對(duì)象是:

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

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

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

在“foo”激活時(shí)(進(jìn)入上下文),“foo”上下文的活動(dòng)對(duì)象是:

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)建時(shí),其[[scope]]為:

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

在“bar”激活時(shí),“bar”上下文的活動(dòng)對(duì)象為:

barContext.AO = {
  z: 30
};

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

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:
barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];

對(duì)“x”、“y”、“z”的標(biāo)識(shí)符解析如下:

- "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í)被存儲(chǔ),與函數(shù)共存亡。實(shí)際上,閉包是函數(shù)代碼和其[[scope]]的結(jié)合。因此,作為其對(duì)象之一,[[Scope]]包括在函數(shù)內(nèi)創(chuàng)建的詞法作用域(父變量對(duì)象)。當(dāng)函數(shù)進(jìn)一步激活時(shí),在變量對(duì)象的這個(gè)詞法鏈(靜態(tài)的存儲(chǔ)于創(chuàng)建時(shí))中,來(lái)自較高作用域的變量將被搜尋。

例如:

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

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

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

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

在上面的例子中,我們看到,在函數(shù)創(chuàng)建時(shí)獲得函數(shù)的[[scope]]屬性,通過(guò)該屬性訪問(wèn)到所有父上下文的變量。但是,這個(gè)規(guī)則有一個(gè)重要的例外,它涉及到通過(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ù)表達(dá)式
    alert(x);
    alert(y);
  };
  var barFn = Function('alert(x); alert(y);');
  barFD(); // 10, 20
  barFE(); // 10, 20
  barFn(); // 10, "y" is not defined
}
foo();

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

二維作用域鏈查找

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

function foo() {
  alert(x);
}
Object.prototype.x = 10;
foo(); // 10

活動(dòng)對(duì)象沒(méi)有原型,我們可以在下面的例子中看到:

function foo() {
  var x = 20;
  function bar() {
    alert(x);
  }
  bar();
}
Object.prototype.x = 10;
foo(); // 20

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

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

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

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

globalContext.Scope = [
  Global
];
evalContext.Scope === callingContext.Scope;

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

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

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

在這個(gè)例子中添加對(duì)象,對(duì)象是它的參數(shù)(這樣,沒(méi)有前綴,這個(gè)對(duì)象的屬性變得可以訪問(wèn))。

var foo = {x: 10, y: 20};
with (foo) {
  alert(x); // 10
  alert(y); // 20
}

作用域鏈修改成這樣:

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

我們?cè)俅慰吹剑ㄟ^(guò) with 語(yǔ)句,對(duì)象中標(biāo)識(shí)符的解析添加到作用域鏈的最前端:

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

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

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

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

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

作用域鏈修改為:

var catchObject = {
  ex: <exception object>
};
Scope = catchObject + AO|VO + [[Scope]]

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

結(jié)論

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

其它參考

  1. [[Scope]]
  2. Scope Chain and Identifier Resolution