[JavaScript] Javascript 的作用域 (Scope) 與範圍鏈 (Scope Chain):往外找
Outline
- Scope 是什麼?
- 範圍鏈 (Scope Chain)
- ES6: const 與 let
上一篇的:[JavaScript] Javascript 的執行環境 (Execution context) 與堆疊 (Stack) 理解之後,接著來看 Scope chain。
Scope 是什麼?
Javascript 的 scope 是什麼?可稱為作用域 / 範疇 / 生存範圍 / 存在範圍,意思是變數可以被取用 (accessibility),或者可以被看到 (visibility) 的有效範圍。
看看 w3c 上的怎麼說:
Scope determines the accessibility (visibility) of variables.
Scope 定義了變數的可及與可視範圍。
Scope 的單位共分為兩種:
一、全域作用域 Global Scope
Global Scope 意即不在函式或區塊內宣告的變數,可以在任何地方取用變數的範圍:
除了在最外層直接宣告變數之外,如果在定義變數時前面沒有加上宣告方式,即使是在函式內,也會成為全域變數,一般不建議這樣的寫法,以免環境汙染:
順帶一提,如果是嚴謹模式(Strict Mode)下,則無法如上述不選定宣告方式來宣告變數,會跳出錯誤。
二、區域作用域 Local Scope
Local Scope 則為特定範圍內才可取用的作用域。
由於 ES6 的 const、let,這兩種方式宣告和由 var 所宣告的變數有效範圍略有不同,故又分為函式作用域 (Function Scope) 和區塊作用域 (Block Scope)。
notes: w3c對於local的定義為函式內的範圍,mdn的定義則為非全域作用域的都算是區域作用域,兩者之間的差異沒有關係,釐清函式作用域和區塊作用域的意思就可以了。
函式作用域 Function Scope
在函式內宣告的變數,僅在函式內可取用。
區塊作用域 (Block Scope)
區塊的意思就是大括弧 (curly brace) {}
,只有在這個大括弧內才可取用。
順帶一提,let 和 const 的宣告方式如果重複宣告,也是會跳出錯誤的:
如果是以 var 的方式宣告,則後者會覆蓋前者。
如果剛剛的範例中以 var 來宣告,則會發現在大括弧外一樣可以取用變數,因為 var 不是區塊作用域,而是函式作用域。
範圍鏈 (Scope Chain)
看完了 Scope 的含意,接著來看 Scope Chain。
如果像是這樣的程式,跑起來會是什麼結果呢?
undefined
: 因為變數 me 在 func b 裡面沒有被宣告過emma2
:因為 func a 裡面已經宣告新的 me 的值,然後再呼叫 func bemma1
:因為全域環境下 me 是 emma1
印出來的答案是 3 : emma1
,跟你想像的一樣嗎?為什麼?
因為在 func b 運行的時候,JS 引擎替 func b 建立了他的函式執行環境,而在建立執行環境的時候,也替他建立了 this 和「外部環境參考 ( Reference to Outer Environment」,對 func b 來說,他自己沒有 me 這個變數,所以他往外部參考環境找,而他的外部環境是全域,不是 func a,所以 func b 取得的是全域的 emma1。
為什麼 func b 的外部環境是全域環境而不是 func a ?因為 Javascript 在決定外部參考環境的時候是以「詞彙環境 (Lexical Environment)」為準則的。
詞彙環境 (Lexical Environment)
直白一點理解,詞彙環境就是程式碼實際上、物理上,到底寫在哪裡,
再看一次剛剛的程式碼,所以 func b 所在的位置,其實跟全域的 me ,或是 func a ,都是在同一個階層的,func a 和 func b 的外部參考環境,都是全域,不管他是在什麼位置被呼叫的。
而 func b 這個往外找的這個機制,就是所謂的範圍鏈 (Scope Chain)。
大概理解到這邊就可以了,對詞彙環境想要理解更多一點的可以再往下看。
詞彙環境 (Lexical Environment)
如果你試著搜尋 Lexical Environment,會看到很多人說,Lexical Environment 就是你的程式碼物理上 (physically)、實際上寫在哪裡,而 ECMA 上是這麼說的:
A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.
詞彙環境是用來定義識別字 (Identifier) 與變量 (variables) 和函數 (functions) 之間的關聯的。並詞彙環境以環境紀錄 (Environment Record) 和外部環境參考 (Outer Lexical Environment Reference) 組成。
在一個執行環境建立的時候,也會建立這個環境的詞彙環境,所以白話一點說,詞彙環境就是在記錄執行環境中宣告的名稱與變數和函式們之間的關聯等,還有外部參考環境。所以才可以說,他是程式碼實際上寫在哪裡的意思。
如果你真的搜尋了,那你可能還會看到另外一個字:詞彙作用域 Lexical Scope。
詞彙環境 (Lexical Environment) = 語彙範疇 (Lexical Scope) ?
先說答案:NO
語彙範疇 Lexical Scope
又稱為靜態範疇 (Static Scope),意指在我們打好程式碼進行編譯時,作用域就已經依我們程式碼的內容決定好了。相對於語彙範疇則有動態範疇 (Dynamic Scope),Javascript 與大多數的語言多採用靜態範疇,動態範疇的語言則要直到程式執行該位置才能決定作用域。
Lexical Environment 和 Lexical Scope 之間最大的差異,就是 Lexical Environment 是在程式執行中存放環境資訊的地方,而 Lexical Scope 則為在程式編譯時就已經決定好的作用域。
並語彙範疇 (Lexical Scope) 具有一個特性:裡面的可以拿外面的,外面的不能拿裡面的,意思是如果 func A 包著 func B,各具有各自的變數,裡面的 func B 可以拿到外面 func A 的變數,A 拿不到 B 的,舉例:
以上的內容,執行結果會是:
func B 裡面的 varA 跟 varB 都拿到了,但是 func A 拿不到 func B 裡面的 varB,得證以上的意思。
結論
Scope 是變數的取用範圍,而程式的詞彙環境 (Lexical Environment) 決定了這個範圍,Javascript 引擎會依著範圍鏈 (Scope chain) 尋找可取用的變數。
Javascript 為靜態範疇 (Lexical Scope),所以在程式執行的時候就已經決定了所有變數的 Scope。
內容若有任何錯誤,歡迎留言交流指教! 🐊
ref:
JavaScript 全攻略:克服 JS 的奇怪部分
w3c-JavaScript Scope
Understanding Scope and Scope Chain in JavaScript
JavaScript Scope and Closures
Standard ECMA-262-10.2-Lexical Environments
圖解JS詞法環境(Lexical environment)
stackoverflow-Lexical environment and function scope
stackoverflow-What is lexical scope?
JavaScript — Lexical Scope
w3c-Javascript Syntax
mdn-識別字
你懂 JavaScript 嗎?#12 函式範疇與區塊範疇(Function vs Block Scope)
mdn-Scope
mdn-作用域
你懂 JavaScript 嗎?#10 範疇(Scope)