[JS] Scope & Scope chain
JavaScript 之 Scope 作用域
這裡先針對 JavaScript 的作用域進行說明,但作用域並不是 JS 語言才有,作用域簡單來說就是「一個變數能夠被存取的範圍,如果超出該範圍就不能存取到該變數」,或者也有人說作用域就是一個變數的生存範圍。
在 ES6 之前,唯一能夠產生出作用域的是 function,並以此區分為 local scope (區域作用域) 及 global scope (全域作用域),兩者區別看以下的範例來了解
1 | var a = 'global'; |
可以看到在 function test
能夠存取到本地的 b
及外部的 a
,但在 test
之外的地方卻存取不到 b
。
這裡可以看出來在 function 裡面的範圍便是 local scope,而 function 外則為 global scope,在全域作用域宣告的變數稱為 global variable (全域變數) 在代碼中的任何地方都能夠存取到,例如範例中的 a
即為全域變數。
另外要注意的是一個 function 的作用域除了 {...}
之外,還包括了參數的部分也是作用域的範圍。
再看下面這個範例
1 | if (true) { |
可能會錯覺以為結果像 function 一樣存取不到在 if 裡面的變數,但前面說過在 ES6 之前,唯一能夠產生出作用域的是 function,所以 if、for、switch、while 等等是界定不出作用域的。
不過以上為 ES6 之前的情形,在 ES6 之後新增了 block scope 區塊作用域的概念,使得有使用區塊 {...}
語法,也就是 if、for、switch、while 等也能夠界定出作用域,但必須配合 let
、const
關鍵字使用,但這部分並非本文主要內容就不多贅述。
Scope Chain 作用域鏈
這裡再看以下代碼
1 | var a = 10; |
在 inner
列印 a
的結果是列印 outer
的 5 ,而不是在全域的 10,會有這樣的結果是因為在 inner 的作用域裡面找不到 a
,就會去上一層的作用域,也就是 outer
的作用域尋找,如果還是找不到就再往上一層作用域找,直到找到為止或是最終到全域仍找不到就會拋出 Uncaught ReferenceError: a is not defined
的錯誤。
而像這樣的過程便是 scope chain (作用域鏈),形成 inner scope -> outer function scope -> global scope 這樣的作用域鏈。
Scope 作用域
前面說的 scope 主要是針對 JavaScript 的情況,但 scope 並不限於 JavaScript 語言才有,不同的程式語言可能有不同的作用域,甚至同一語言內也可能存在多種作用域。這裡再說一次 scope 作用域指的是變數或常數能夠被存取到的範圍。
scope 基本可以分為
- Static Scope (靜態作用域,或者也有人稱其為 Lexical Scope 語彙作用域、詞法作用域)
- Dynamic Scope (動態作用域)
因為 JavaScript 是採用 Lexical Scope,所以後面再針對靜態作用域說明。
Static Scope 靜態作用域
先來看看下面的代碼
1 | var a = 10; |
請問此時的 test()
的結果會是什麼? 正確答案是 10,有人可能認為在 test
裡的 print
找不到 a
便往上一層的 test
作用域找到 a
為 20,如果從前面學的 scope chain 來看似乎沒錯,但結果卻是 10,這就與使用的程式語言是採用何種 scope 有關,在這裡即是 static scope。
採用 static scope 的程式在編譯時就能夠確定作用域,也就是能決定在某範圍是否能夠存取某些變數,且該作用域與 function 在哪裡被呼叫無關,而是與在哪裡宣告(定義)有關。
像是 print
定義時,print
的作用域找不到 a
所以往上一層的 global scope 找到 a
為 10,從作用域鏈來看就是 print scope -> global scope,print
對於變數的存取只能在這兩個作用域尋找。
比較下面的代碼
1 | function print() { |
這次你能看出來會印出什麼? 20?
如果能夠明白上面對於 static scope 的解釋,那麼你應該能知道結果會是顯示 Uncaught ReferenceError: b is not defined
的錯誤。
因為 print
不論是在 print scope,還是 global scope 都找不到變數 b
的存在。
而與 static scope 相反的存在便是 dynamic scope 動態作用域,無法在執行前便知曉函式裡的變數是什麼值,只能在執行時動態的決定,例如使用 dynamic scope 的程式語言執行上面的代碼,結果便會是 20。
參考資料: