幾乎全部編程語言最基本的功能之一,就是可以儲存變量當中的值,而且能在以後對這個值進行訪問或修改。事實上,正是這種儲存和訪問變量的值的能力將狀態帶給了程序。react
可是將變量引入程序會引發幾個頗有意思的問題,也正是咱們將要討論的:這些變量住在哪裏?換句話說,它們儲存在哪裏?最重要的是,程序須要時如何找到它們?git
這些問題說明須要一套設計良好的規則來存儲變量,而且以後能夠方便地找到這些變量。這套規則被稱爲做用域。github
雖然你們常說 JavaScript 爲動態語言,但事實上 JavaScript 爲一門編譯語言。它不會像其餘語言同樣提早編譯,它會在代碼執行前進行編譯。編程
在傳統編譯語言的流程中,程序中的源代碼在編譯過程當中通常會進行三個步驟。bash
!這裏不作過多解釋編程語言
理解 JavaScript 做用域,要明白做用域在 js 中起到做用以及其 js 之間的關係。首先這裏介紹 js 的三個重要角色。函數
引擎post
從頭至尾負責整個 JavaScript 程序的編譯及執行過程。spa
編譯器設計
引擎的好朋友之一,負責語法分析及代碼生成等髒活累活(詳見前一節的內容)。
做用域
引擎的另外一位好朋友,負責收集並維護由全部聲明的標識符(變量)組成的一系列查 詢,並實施一套很是嚴格的規則,肯定當前執行的代碼對這些標識符的訪問權限。
爲了可以徹底理解 JavaScript 的工做原理,你須要開始像引擎(和它的朋友們)同樣思考, 從它們的角度提出問題,並從它們的角度回答這些問題。
當咱們運行到 var a = 2 時,這裏會被解釋爲:
遇到 var a,編譯器會
詢問做用域
是否已經有一個該名稱的變量存在於同一個做用域的 集合中。若是是,編譯器會忽略該聲明,繼續進行編譯;不然它會要求做用域在當前做 用域的集合中聲明一個新的變量,並命名爲 a。
接下來編譯器
會爲引擎生成運行時
所需的代碼,這些代碼被用來處理 a = 2
這個賦值 操做。引擎運行時會首先詢問做用域
,在當前的做用域集合中是否存在一個叫做 a 的 變量。若是是,引擎就會使用這個變量;若是否,引擎會繼續查找該變量。
總結
變量的賦值操做會執行兩個動做,首先編譯器會在當前做用域中聲明一個變量(如 果以前沒有聲明過),而後在運行時引擎會在做用域中查找該變量,若是可以找到就會對 它賦值
/**
* 這裏對 a 進行了LHS查詢
* 當變量值出如今賦值操做符左側時會進行LHS
**/
var a = 2
/**
* 這裏對 a 進行了RHS查詢
* RHS 可譯爲(取到它的源值,獲取某某的值)
**/
console.log(a)
/**
* 這裏包含了一個隱藏的LHS
* 當執行demo(...)時,會隱式的執行 a = 2
**/
function demo(a) {
console.log(a)
}
demo(2)
複製代碼
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
複製代碼
讓咱們把上面這段代碼的處理過程想象成一段對話,這段對話多是下面這樣的。
當一個塊或函數嵌套在另外一個塊或函數中時,就發生了做用域的嵌套。所以,在當前做用 域中沒法找到某個變量時,引擎就會在外層嵌套的做用域中繼續查找,直到找到該變量, 或抵達最外層的做用域(也就是全局做用域)爲止。
LHS 和 RHS 引用都會在當前做用域進行查找,若是沒有找到,就會像冒泡同樣前往上一層做用域, 若是仍是沒有找到就繼續向上,以此類推。一旦抵達頂層(全局做用域),可能找到了你 所需的變量,也可能沒找到,但不管如何查找過程都將中止。
function foo(a) {
console.log( a + b );
b = a;
}
foo( 2 );
複製代碼
當代碼對 b 執行 RHS 查詢時 遍歷做用域也沒法找到,此時會拋出 ReferenceError 異常。 可是當程序在執行 LHS 查詢時 遍歷做用域一樣沒法找到,在非嚴格模式下
全局做用域會建立一個具備更名稱的變量。
⚠️ 當 RHS 查找到一個指定變量,可是對該變量執行不合理操做時,會拋出 TypeError 異常。因此 ReferenceError 表明查找失敗。TypeError 則查找成功。