做用域鏈

《JavaScript深刻之執行上下文棧》中講到,當JavaScript代碼執行一段可執行代碼(executable code)時,會建立對應的執行上下文(execution context)。前端

對於每一個執行上下文,都有三個重要屬性:git

  • 變量對象(Variable object,VO)
  • 做用域鏈(Scope chain)
  • this

今天重點講講做用域鏈。github

做用域鏈

《JavaScript深刻之變量對象》中講到,當查找變量的時候,會先從當前上下文的變量對象中查找,若是沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鏈表就叫作做用域鏈。函數

下面,讓咱們以一個函數的建立和激活兩個時期來說解做用域鏈是如何建立和變化的。this

函數建立

《JavaScript深刻之詞法做用域和動態做用域》中講到,函數的做用域在函數定義的時候就決定了。spa

這是由於函數有一個內部屬性 [[scope]],當函數建立的時候,就會保存全部父變量對象到其中,你能夠理解 [[scope]] 就是全部父變量對象的層級鏈,可是注意:[[scope]] 並不表明完整的做用域鏈!code

舉個例子:對象

 
function foo() { function bar() { ... } } 

函數建立時,各自的[[scope]]爲:ip

foo.[[scope]] = [
  globalContext.VO ]; bar.[[scope]] = [ fooContext.AO, globalContext.VO ]; 

函數激活

當函數激活時,進入函數上下文,建立 VO/AO 後,就會將活動對象添加到做用鏈的前端。作用域

這時候執行上下文的做用域鏈,咱們命名爲 Scope:

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

至此,做用域鏈建立完畢。

捋一捋

如下面的例子爲例,結合着以前講的變量對象和執行上下文棧,咱們來總結一下函數執行上下文中做用域鏈和變量對象的建立過程:

var scope = "global scope"; function checkscope(){ var scope2 = 'local scope'; return scope2; } checkscope();

執行過程以下:

1.checkscope 函數被建立,保存做用域鏈到 內部屬性[[scope]]

checkscope.[[scope]] = [
    globalContext.VO ];

2.執行 checkscope 函數,建立 checkscope 函數執行上下文,checkscope 函數執行上下文被壓入執行上下文棧

ECStack = [
    checkscopeContext,
    globalContext
];

3.checkscope 函數並不馬上執行,開始作準備工做,第一步:複製函數[[scope]]屬性建立做用域鏈

checkscopeContext = {
    Scope: checkscope.[[scope]], }

4.第二步:用 arguments 建立活動對象,隨後初始化活動對象,加入形參、函數聲明、變量聲明

checkscopeContext = {
    AO: { arguments: { length: 0 }, scope2: undefined } }

5.第三步:將活動對象壓入 checkscope 做用域鏈頂端

checkscopeContext = {
    AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]] }

6.準備工做作完,開始執行函數,隨着函數的執行,修改 AO 的屬性值

checkscopeContext = {
    AO: { arguments: { length: 0 }, scope2: 'local scope' }, Scope: [AO, [[Scope]]] }

7.查找到 scope2 的值,返回後函數執行完畢,函數上下文從執行上下文棧中彈出

ECStack = [
    globalContext
];
相關文章
相關標籤/搜索