Day15 | JS 的執行環境、作用域、範圍鏈
❒ 執行的錯誤情境 LHS, RHS
在 JavaScript 遇到錯誤記得修正,不然後方的程式碼都不會運行,這時也可以運用 LHS, RHS 提示來進行排除。
範例 1
1 | var ming = '小明'; |
- ming 在取值時就會稱為 RHS。
- 小明賦予到左邊變數
ming
上稱為 LHS。
範例 2 | 延續上題
1 | var ming = '小明'; |
- 右邊的值
ming
就是使用 RHS 取得這個變數,並且透過 LHS 賦予到左邊的變數。 - 在等號
=
的右邊或是函式取得變數上,都可以稱為 RHS。
範例 3
1 | 1 = true; |
解析:
- 當左側不是變數時,就無法被賦予值,會顯示錯誤訊息
Uncaught SyntaxError: Invalid left-hand side in assignment
。- 看到 LHS 錯誤時,可以看一下左邊的值是不是沒有辦法被賦予。
答案:
1 = true;
在編譯時產生錯誤Uncaught SyntaxError: Invalid left-hand side in assignment
→ LHS 錯誤。console.log(a);
不會在編譯過程產生錯誤,會在執行階段發現變數無法取得而產生錯誤訊息Uncaught ReferenceError: a is not defined
,表示此變數是沒有被定義過的,在 JS 運行過程中也無法找到此變數 → RHS 錯誤 。
❒ 語法作用域 ( Lexical scope )
- JavaScript 採用語法作用域,也稱靜態作用域。
- 語法作用域會牽扯到靜態作用域與動態作用域。
靜態作用域
- 變數的作用域在語法解析時,就已經確定作用域。
- JavaScript 是直譯式語言或透過直譯器來生成代碼,並運行代碼。語法作用域也稱靜態作用域 ( 語法解析時就已經確定作用域 ),寫 function 時作用域就已經確定 ( 下方範例 )。
1 | // 範例一 |
動態作用域
- 變數作用域在函式調用時才會決定它的作用域。
作用域
- JavaScript 的作用域是一層一層向內的。外層有一層全域作用域 → 內層由函式所包覆。
- 上圖中兩個 function 的作用域是獨立的,如果作用域內有需要一些變數,但這作用域內沒有特定的變數時會向外層 ( 全域 ) 尋找,如果外層 ( 全域 ) 有可以使用就會直接使用,否則會顯示
ReferenceError: xxx is not defined
。
作用域 | 範例 1. ( 靜態作用域 )
1 | var value = 1; |
執行順序 :
- 運行順序先宣告 value 等於 1 。
- 執行
fn2()
,再把value
重新宣告等於 2,再執行fn1()
。 - 這時
fn1()
內value
值會等於 1。
解說 :
- 因為 JS 是屬於靜態作用域,所以作用域在撰寫 funciton 時就已經確定。因此
var value = 1;
作用域包含全部,所以無論在fn1()
或fn2()
都可以讀取到。 - 雖然在
fn2()
重新賦予value = 2
的值,但value = 2
的值作用域只在fn2()
內。
所以再往下執行fn1()
時,fn1()
內的console.log(value);
就會向外查找value
等於 1 的值。
▶️ 所以答案會印出 1。
❒ 執行環境與執行堆疊
函式執行環境
1 | // 範例 |
- 上方介紹到函式作用域是限制在 function 內,所以在 function 宣告任何變數,它的作用域就會限制在 function 內。
- ‼️ 注意 : 如果我們沒有執行這段函式「
callName();
」,它是不會有任何變數產生。所以是需要執行它「 呼叫callName();
」才會產生執行環境,這個執行環境內才會有屬於它的變數。
另外它「callName();
」是可以重複調用的。 - 會產生一個
this
,後方會介紹到。
全域執行環境與堆疊
- 除了函式,全域也有屬於自己的全域執行環境。
- 全域執行環境是在網頁一開啟或是後端的 Node.js 一開啟時,它的執行環境就已經建立了。它在建立時會同時產生一個 window 變數 ( 使用瀏覽器開啟 ) 或是 global 變數 ( 使用 Node JS 開啟 ) 。
- 會產生一個
this
,等於 window 或 global。- 但注意
this
會隨著它的執行環境而有所不同,後方章節會介紹到。
- 但注意
1 | // 範例 |
運行順序 :
- 上方兩個函式,先運行
doSomething()
。 doSomething()
裡面再運行sayHi();
。
解說 :
- 在執行環境堆疊的狀態,會先看到上方運行順序。
- 網頁一開啟就會產生全域執行環境
- 呼叫
doSomething()
,出現doSomething()
的執行環境,並且堆疊在全域的執行環境上。 doSomething()
內去呼叫sayHi();
這個函式,sayHi();
會堆疊在doSomething()
這個函式上。
- 呼叫
- 所以這個執行環境是一層一層堆疊起來的,和函式在宣告的時候沒有關聯性,而是與呼叫的位置有關係。
離開時也是一層一層的離開sayHi();
執行完就會先離開 →doSomething()
完成後也會離開 → 最後回到全域的執行環境。
❒ 範圍鍊
解說範圍鍊
- 當函式的本身沒有這個變數時,它就會向外層來做尋找,這尋找的過程與執行環境是沒有關聯性的。
- 所以下方範例中,無論是函式 fn1 或函式 fn2 的範圍鍊都是指向外層的全域環境。
範圍鍊範例 1
1 | var value = 1; |
- 一開始會產生全域執行環境。
- 執行
fn2();
的執行環境 → 執行fn2()
內的fn1();
,雖然在fn2()
重新賦予value = 2
的值,但value = 2
的值作用域只在fn2()
內。
所以再往下執行fn1()
時,fn1()
內的console.log(value);
就會向外查找value
等於 1 的值。 - ‼️ 注意 : 函式
fn1
在往外層尋找value
時並不會跟執行環境有任何的關聯性。因為 JS 是語法作用域,它在程式碼撰寫時就已經確定它的作用域。- 函式
fn1
沒有value
這個變數時會向外層全域來做尋找,這尋找過程與執行環境 ( 執行環境也就是function fn1(){}
內 ) 是沒有關聯性的。
- 函式
範圍鍊範例 02
1 | var person = '老媽'; |
答案
hi 老媽、hi 老媽、哈囉~漂亮阿姨
doMorningWork()
內的sayHi();
會忽視裏面的變數 person 老爸,向外層尋找變數 value 老媽。因為變數 person 老爸作用域只在doMorningWork()
內。
另外函式 sayHi 向外尋找變數 person 老媽是因為它裡面本身沒有此變數。doMorningWork()
內宣告一個meetAuntie()
,所以現在執行的不是外層的 sayHi 而是內層的meetAuntie()
,而內層的meetAuntie()
本身就有一個 person 變數漂亮阿姨,所以會印出哈囉~漂亮阿姨
。- 如果註解掉
var person = '漂亮阿姨';
就會向外尋找變數印出哈囉~老爸
。
- 如果註解掉
- 結論 : 當函式的本身沒有這個變數時,它就會向外層來做尋找,這尋找的過程與執行環境是沒有關聯性的。
參考資訊
- 六角學院 - JavaScript 核心篇