Scope Chain(做用域鏈)

本章,咱們討論一下ECMAScript中的做用域鏈 , 開門見山。前端

 

 

什麼是做用域鏈數組

i.ECMAScript是容許建立內部函數的,甚至能從父函數中返回這些函數。做用域鏈正是內部上下文中全部變量對象(及自由對象)的列表。此鏈用來變量解析查詢。瀏覽器

 

做用域鏈的特性閉包

i.是執行上下文的一個屬性函數

 

activeExecutionContext = {
      vo : {},  
      this : thisValue,
      scope : []  
}

 

ii.邏輯上是一個數組,每個元素是一個變量對象this

iii.定義爲:Scope = ActiveContext.VO + Function.[[Scope]]  ([[Scope]] 是函數的屬性)spa

理解做用域鏈prototype

i.[[Scope]] 是函數的私有屬性,在函數被解析時建立,不會改變。code

  爲了讓你們更好的理解,先讓你們看一段代碼:對象

 

var x = 'test';

function foo() {
    console.log(x);
}

(function() {
    var x = 'what';

    foo();
})();

 

上面的代碼輸出會是什麼呢?爲何?

控制檯將會輸出 'test',而非 'what' 。這個例子也說明,一個函數的[[Scope]] 持續存在,即便是在函數建立的做用域已經完成以後。

ii.[[Scope]] 「一般」(存在乎外) 包含了父級函數的[[Scope]]屬性,ECMAScript依靠這個特性來實現閉包。

 

iii.[[Scope]]是函數的屬性,這也意味着ECMAScript中沒有Java那樣的塊級做用域。(ES6中對這一塊獲得了增強)只有函數級做用域。

    觀察如下3種函數構造方式的差別:

var x = 10;

function foo() {
     var y = 20;
     
     // 函數聲明方式建立
     function innerFoo() {
          console.log(x ,y);
     }
     
     //函數表達式方式建立
     var innerFoo2 = function() {
          console.log(x ,y);
     } 
     
     //構造函數方式建立
     var innerFoo3 = Function('console.log(x);console.log(y);'); 

    innerFoo(); //10 20
    innerFoo2(); //10 20
    innerFoo3(); //10 ,y not defined
}

經過以上代碼,咱們能夠看出,經過Function構造函數建立的方法只擁有全局做用域

iiii.變量的二維鏈式查找

   變量的解析是經過做用域鏈來實現的。

     變量本質是上以變量對象的屬性方式存在,當變量對象與JavaScript中對象重疊時,它就會自然的受到原型鏈的影響。

一段有趣的代碼:

function foo() {
    console.log(x);
}

Object.prototype.x = 10;

foo(); //10

緣由: 此時,全局對象爲 window(假設在瀏覽器中運行該段代碼),而window對象是Object所派生的。根據原型鏈查找規則,實例中訪問不到的屬性和方法,將會在原型中查找。

如下面的代碼爲例,其查找順序是這樣的:

iiiii.全局代碼和 eval 的做用域鏈

     全局代碼中的做用域鏈僅包含全局對象

     eval的上下文與當前 calling context(調用上下文) 擁有相同的做用域鏈

     ES5中規定,若是對eval創建別名(非直接調用),這時做用域鏈僅包含全局對象

iiiiii.做用域鏈是能夠在運行時動態改變的

     在with 和 catch 語句中:Scope = withObject || catchObject + VO + [[Scope]]

     大多數狀況下是不變的,但在with語句和catch語句塊中,能夠改變做用域鏈。這種技巧在有些時候很是有用,但大多數狀況下,咱們要儘量避免

     ES5中,經過詞法環境、詞法環境記錄的方式來掃描這種變化

根據咱們上面講到的,你們看看以下代碼:

var x = 10,
      y = 10;

with({x: 20}) {
     var x = 30 ,
           y = 30;
     console.log(x ,y); // 30 30
}

console.log(x ,y);  // 10 30

輸出的結果是: 30 30   10 30 ,爲何呢?實際上上面講到的做用域鏈的動態改變時,已經對該問題作出瞭解答, 下面咱們分析一下:

在with和catch語塊中的做用域鏈:

    1.x = 10 ,y = 10;

    2.對象{x:20}被添加到了做用域的前端

    3.在with內部,遇到了var聲明。可是什麼也沒建立,由於在進入上下文時,全部變量已被解析添加

    4.在步驟2中,僅修改變量'x' ,實際上對象中的'x'如今被解析,並添加到做用域鏈的前端,'x'由 20 變爲 30

    5.一樣也有變量對象的屬性'y'的修改,被解析後其值也由10變爲30

    6.此外,在with聲明完後,它的特定對象從做用域鏈中移除,(已改變的變量「x」--30也從那個對象中移除),即做用域鏈的結構恢復到with獲得增強之前的狀態。

    7.最後console中,當前變量對象 'x'保持相同,'y'的值 = 30(在with聲明運行中已發生改變)

iiiiiii.結合this一塊兒

    直接調用函數,做用域爲 withObject

var x = 10; 

with({
    foo : function() {
         console.log(this.x);
    } ,
    x : 20
    }) {
   foo(); // 20
}    

    

總結

理解執行上下文、VO(變量對象)、this、做用域鏈是理解JavaScript執行的基礎,尤爲是this和做用域鏈

相關文章
相關標籤/搜索