細讀《你不知道的JavaScript·上卷》1-1 做用域是什麼?

墨言妹帶你細讀《你不知道的 JavaScript 》系列的世界,深刻 JavaScript 語言內部,弄清楚 JavaScript 每個零部件的用途,知其然更要知其因此然。git

做用域是什麼

  • 問題1:變量存儲在哪裏?
  • 問題2:程序須要時如何找到它們?

1.1 編譯原理

一般,把 JavaScript 歸類爲 「 動態 」 或 「 解釋執行 」 的語言,可是事實上它是一門 編譯語言,不提早編譯,編譯結果也不在分佈式系統中進行移植。github

JavaScript 引擎進行編譯的步驟和傳統的編譯語言很是類似,在某些環節比它要複雜。 編程

傳統編譯語言,在執行以前的三個步驟,統稱爲 「 編譯 」 。數組

  • 分詞/詞法分析( Tokenizing/Lexingbash

    將有字符組成的字符串分解成(對編程語言來講)有意義的代碼塊,這些代碼塊被稱爲詞法單元( token )。編程語言

    var a = 2;
    複製代碼

    被分解成詞法單元:vara=2; 。空格在該語言中有意義,則會被當作詞法單元,不然不是。分佈式

  • 解析/語法分析( Parsing函數

    將詞法單元流(數組)轉換成一個由元素逐級嵌套所組成的表明了程序語法結構的 「 抽象語法樹 」( Abstract Syntax Tree , AST )。post

    var a = 2;
    複製代碼

    以上代碼的抽象語法樹以下所示:性能

    • VariableDeclaration 頂級節點
      • Identifer 子節點,值爲 a
      • AssignmentExpression 子節點
        • NumericLiteral 子節點,值爲 2
  • 代碼生成

    AST 轉換爲可執代碼的過程被稱爲代碼生成。這個過程與語言、目標平臺等相關。

    即經過某種方法,將 var a = 2 ;AST 轉化爲一組機器指令,用來建立一個變量 a ,並將值存儲在 a 中。

    引擎,能夠根據須要建立並存儲變量。

1.2 理解做用域

1.2.1 演員表

  • 引擎,從頭至尾負責整個 JavaScript 程序的編譯及執行過程。
  • 編譯器,負責語法分析及代碼生成等髒活累活。
  • 做用域,負責收集並維護由全部聲明的標識符(變量)組成一系列查詢,並實施一套很是嚴格的規則,肯定當前執行的代碼對這些標識符的訪問權限。

1.2.2 對話

JavaScript 引擎是如何處理 JavaScript 代碼的?

好比 var a = 2; 存在2個不一樣的聲明,變量的賦值操做會執行兩個動做。

  • 遇到 var a ,在編譯階段,編譯器先詢問當前做用域,在做用域集合中是否存在變量 a ,若不存在則聲明一個新變量名爲 a;接着編譯器會爲引擎生成運行時所需的代碼,處理 a = 2 這個賦值操做。
  • 遇到 ( a = 2 ),在執行階段,引擎運行時先詢問做用域,在做用域中查找該變量 a,若是找到就將值 2 賦值給變量 a,不然引擎就會舉手示意並拋出一個異常。

1.2.3 編譯器有話說

  • 如何理解引擎、編譯器、做用域的關係

    • 代碼先編譯後執行,當編譯器在編譯過程的第二步中生成了代碼,引擎執行它時,會經過查找變量 a 來判斷它是否已經聲明過。
    • 查找的過程由做用域進行協助,可是引擎執行怎樣的查找,會影響最終的查找結果。對 JavaScript 引擎的性能要求很高。
  • 引擎查找的兩種方式: RHSLHS

    • LHS 查詢(左側):找到變量的容器自己,而後對其賦值,即賦值操做的目標是誰。好比 a = 2; ,爲 = 2 這個賦值操做找到一個目標。
    • RHS查詢(非左側):查找某個變量的值,理解爲 retrieve his source value ,即誰是賦值操做的源頭。好比: console.log( a ); ,須要獲取到變量 a 的值,則對變量 aRHS 查詢,並傳值給 console.log(...)
function foo(a){
	console.log( a ); //2
}
foo(2);
複製代碼

上述代碼共有1處 LHS 查詢,3處 RHS 查詢。

  • LHS 查詢有:
    • 隱式的 a = 2 中,在 2 被當作參數傳遞給 foo(...) 函數時,須要對參數 a 進行 LHS 查詢
  • RHS 查詢有:
    • 最後一行 foo(...) 函數的調用須要對 foo 進行 RHS 查詢,意味着 「去找到 foo 的值,並把它給我 」 ,而且 (...) 意味着 foo 的值須要被執行,所以它最好真的示意函數類型的值。
    • console.log( a ); 中對 a 進行 RHS查詢,而且將獲得的值傳給了 console.log(...)
    • console.log(...) 自己對 console 對象進行 RHS 查詢,而且檢查獲得的值中是否有一個叫做 log 的方法。

1.3 做用域嵌套

做用域是一套規則,用於肯定在何處以及如何查找變量(標識符)。當一個塊或函數嵌套在另外一個塊或函數中時,就發生了做用域的嵌套。

  • 若是查找的目的是對變量進行賦值,那麼就會使用 LHS 查詢;若是目的是獲取變量的值,就會使用 RHS 查詢。
  • 賦值操做符會致使 LHS查詢。= 操做符或調用函數時傳入參數的操做都會致使關聯做用域的賦值操做。

遍歷嵌套做用域鏈的規則: 引擎從當前的執行做用域開始查找變量,若是找不到,就向上一級繼續查找。當抵達最外層的全局做用域時,不管找到仍是沒找到,查找過程都會中止。

1.4 異常

爲何區分 LHSRHS ?變量尚未聲明(在任何做用域中都沒法找到該變量)的狀況下,這兩種查詢的行爲是不同的。

function foo(a){
	console.log(a + b);
	b = a;
}
foo(2);
複製代碼

對一個 「未聲明 」 的變量 b 進行 RHS 查詢時,在任何相關的做用域中都沒法找到它。

ReferenceError 和做用域判別失敗相關,而 TypeError 則表明做用域判別成功了,可是對結果的操做是非法或不合理的。

  • RHS 查詢在做用域鏈中搜索不到所需的變量,引擎會拋出 ReferenceError 異常。
  • 非嚴格模式下,LHS 查詢在做用域鏈中搜索不到所需的變量,全局做用域中會建立一個具備該名稱的變量並返還給引擎。
  • 嚴格模式下( ES5 開始,禁止自動或隱式地建立全局變量), LHS 查詢失敗並不會建立並返回一個全局變量,引擎會拋出同 RHS 查詢失敗時相似的 ReferenceError 異常。
  • RHS 查詢成功狀況下,對變量進行不合理的操做,引擎會拋出 TypeError 異常。(好比對非函數類型的值進行函數調用,或者引用 nullundefined 類型的值中的屬性)。

最後, 書讀百遍其義自見,抱着以教爲學的初衷,不斷反思、刻意練習,若對你有幫助,請點個贊,謝謝您的支持與指教。

參考文獻: 木易楊博客

歷史文章: 【譯】30 Seconds of ES6 (一)

相關文章
相關標籤/搜索