對於幾乎全部編程語言,最基本的功能之一就是可以存儲變量的值,而且能在以後對這個值進行訪問和修改。這樣就會帶來幾個問題,這些變量存儲在哪裏?程序在須要的時候又是如何找到它們的?要解決這些問題,就須要引入一套規則來存儲變量和訪問變量,這套規則就是做用域。javascript
在剛開始接觸 JavaScript 這門語言時,確定會常常接觸到 JavaScript 是動態語言, 是解釋執行的,但事實上 JavaScript 是一門編譯語言。只不過和 Java、C# 這些傳統意義上的編譯語言不一樣,JavaScript 的編譯過程不是發生在構建以前的。大部分狀況下,JavaScript 的編譯過程發生在代碼執行前的很短期內。也就是說 JavaScript 代碼在執行前都要進行編譯。java
爲了更好地理解做用域,咱們須要明確下面幾個概念編程
從頭至尾負責整個 JavaScript 程序的編譯及執行過程微信
負責語法分析及代碼生成等髒活累活編程語言
負責收集並維護由全部聲明的標識符(變量) 組成的一系列查詢, 並實施一套很是嚴格的規則, 肯定當前執行的代碼對這些標識符的訪問權限。函數
下面咱們從引擎、編譯器和做用域的角度,分析 var a = 2
這條聲明語句,看看它們是如何協同完成工做的post
簡單來講,變量的賦值操做會執行兩個動做, 首先編譯器會在當前做用域中聲明一個變量(若是以前沒有聲明過), 而後在運行時引擎會在做用域中查找該變量, 若是可以找到就會對它賦值,不然就會並拋出一個異常。學習
咱們知道引擎查找變量的過程在做用域中進行的,而這個過程一般會涉及多個做用域。ui
當一個塊或函數嵌套在另外一個塊或函數中時, 就發生了做用域的嵌套。 所以, 在當前做用域中沒法找到某個變量時, 引擎就會在外層嵌套的做用域中繼續查找, 直到找到該變量,或抵達最外層的做用域(也就是全局做用域) 爲止。spa
爲了便於理解,能夠將做用域嵌套比喻成一棟高樓,咱們從一樓(當前做用域)開始查找,若是沒有找到,就會前往上一個樓層繼續查找,以此類推。一旦到達頂層(全局做用域),可能找到,也可能沒有找到,查找過程都必須中止。
繼續上文的示例,引擎在執行編譯器生成的代碼時,會經過查找變量 a 來判斷它是否已經聲明過。查找的過程由做用域進行協助, 可是引擎執行怎樣的查找, 會影響最終的查找結果。查找過程分爲兩類:LHS查詢和RHS查詢。其實很簡單,當變量出如今賦值操做的左側時進行 LHS 查詢, 出如今右側時進行 RHS 查詢。
更準確一點的講,** RHS 查詢是查找某個變量的值,而 LHS 查詢是查找變量的容器自己,從而能夠對其賦值。**以下面的示例:
var a = 2; // a: LHS查詢
var b = 3; // b: LHS查詢
a = b; //a: LHS查詢 b:RHS查詢
複製代碼
爲何區分 LHS 和 RHS 是一件重要的事情?
由於在變量尚未聲明(在任何做用域中都沒法找到該變量) 的狀況下, LHS 和 RHS兩種查詢的行爲是不同的。
1.當引擎執行 RHS 查詢時,若是 RHS 查詢在全部嵌套的做用域中遍尋不到所需的變量, 引擎就會拋出 ReferenceError異常。
console.log(a); //ReferenceError: a is not defined
複製代碼
2.當引擎執行 LHS 查詢時, 若是在頂層(全局做用域) 中也沒法找到目標變量,全局做用域中就會建立一個具備該名稱的變量, 並將其返還給引擎, 前提是程序運行在非「嚴格模式」 下。若是在「嚴格模式」下,引擎也會拋出 ReferenceError異常。
//非嚴格模式
var a =2;
b = a;
console.log(b); //2
複製代碼
//嚴格模式
'use strict';
var a =2;
b = a;
console.log(b); //ReferenceError: b is not defined
複製代碼
另外,若是 RHS 查詢找到了一個變量, 可是你嘗試對這個變量的值進行不合理的操做,好比試圖對一個非函數類型的值進行函數調用, 或着引用 null 或 undefined 類型的值中的屬性, 那麼引擎會拋出另一種類型的異常, 叫做TypeError。
ReferenceError 表明做用域判別失敗相關, 而 TypeError 則表明做用域判別成功了, 可是對結果的操做是非法或不合理的。
《你不知道的JavaScript》