--- title: 'JS 核心 1 - 執行環境、作用域 (上)' tags: JS 核心, Javascript description: 2021/02/02 --- JS 進階 -- 執行環境、作用域 (上) === ## JavaScript 是如何運行的 我們撰寫JS無法直接被瀏覽器或電腦給閱讀,要經過**解譯**才能運行這段程式碼。 * **編譯式語言 :** 編譯時可除錯,效能較好。 ![](https://i.imgur.com/HUMgW3l.png) * **JS 屬於直譯式語言 :** 執行前是沒有經過編譯的。 彈性高,不須預先定義型別。 ![](https://i.imgur.com/5fDZZTS.png) * **JS 直譯器轉換過程** ![](https://i.imgur.com/xlGVwoT.png) ## 執行的錯誤情境 LHS, RHS JS 執行時,會有一些與取值和賦值相關的常見錯誤。 ![](https://i.imgur.com/kKFatVJ.png) ``` var ming = ‘小明’; // 屬於LHS, 因為我們將小明賦予到左側的變數ming上 console.log(ming); // 屬於RHS, 從右側變數ming取值 var man = ming; // 等號右邊是RHS取值,將ming的值賦予給變數man則屬於LHS ``` * LHS : 將值賦予給左邊的變數,因此當左邊並不是個變數時,就會出現 LHS error。 ``` '小明' = 1; ``` * RHS : 右邊變數沒有被定義,無法找到此變數。 ``` var ming = '小明'; console.log(min); ``` ## 語法作用域(Lexical scope) ### 語法作用域又分為 動態作用域 和 靜態作用域。 (JS 採用靜態作用域) ![](https://i.imgur.com/Ll1ECj7.png) ### JS 作用域在函式裡面 ``` function callName(){ var Ming = '小明'; // 若在函式內宣告變數,外層讀不到 } callName(); console.log(Ming); // Ming is not defined ``` ### JS 作用域是一層一層向內的 (外圍是全域作用域) (內層都是由function ( ) 所包著) JS 屬於靜態作用域 ( JS 是語法作用域 → 程式撰寫好時就確定了) ![](https://i.imgur.com/uUJx6En.png) ``` var value = 1; // 作用域為外層 function fn1() { console.log(value); // 呼叫value時向外查找,找到全域變數var value = 1; } function fn2() { var value = 2; // 這個值只會在fn2( ) 裡面 fn1(); } fn2(); // 1 ``` ## 執行環境與執行堆疊 ### (函式) 執行環境 一個函式的執行,就會產生一個屬於自己的執行環境,而執行環境有**限制作用域**的功用, 並且會**有自己的 this**。 ### (全域) 執行環境 全域環境在網頁一開啟或後端node.js一開啟時,執行環境已建立,自動產生window (使用瀏覽器) 、 global (使用node.js) 的變數,也會產生 this。 <span class="red">this會隨著執行環境而有所不同。</span> ### 執行堆疊 執行堆疊 **(和呼叫函式的位置有關係)**,執行環境是一層一層堆疊起來的。 最後也是一層一層離開,最後只剩下全域的執行環境。 ![](https://i.imgur.com/QwSDDBr.png) 1. 首先,在瀏覽器開啟時,全域執行環境就會被建立。 2. 接著我們執行doSomething函式,所以doSomething的執行環境被生成,且堆疊在全域執行環境之上。 3. 在doSomething中我們又執行了sayHi,因此sayHi的執行環境被生成,且堆疊在doSomething執行環境之上。 ``` function sayHi(name){ var greeting = 'hi'; return greeting + ''+ name; } function doSomething(){ var mom = '老媽'; console.log(1, sayHi(mom)); } doSomething(); // 1, hi 老媽 ``` 函式沒有透過呼叫是不會執行的,而執行環境可透過不斷呼叫產生。 執行順序: openDoors(); openTheDoor(1); // 傳入1 回傳 '開第 1 扇門' for迴圈 i=2 // 傳入2 回傳 '開第 2 扇門' for迴圈 i=3 // 傳入3 回傳 '開第 3 扇門' for迴圈 i=4 // 傳入4 回傳 '開第 4 扇門' for迴圈 i=5 // 不符合條件 i < 5, 離開迴圈 ``` function openTheDoor(num) { return '開第 ' + num + ' 扇門'; } function openDoors() { openTheDoor(1); for (var i = 2; i < 5; i++) { openTheDoor(i); } } openDoors(); ``` ## 範圍鍊 > <span class="red">**範圍鍊是依據函式文法本身來決定,與執行環境無關**</span> ### 範圍鍊概念 * 當函式本身沒有這個變數的時候,就會向外層做尋找。 * 若是函式內的變數找不到值,就會往外一層函式查找,直到全域環境有找到或是沒找到 (undefined)。 :bulb: **範例一** : 1. sayHi( ) 本身沒有person這個變數,所以會向外尋找,person 指向全域的老媽。 2. 此執行環境 doMorningWork( ) 並不會影響sayHi( ) 的範圍練,所以 sayHi( ) 會向外尋找。 ```typescript var person = '老媽'; function sayHi(){ console.log('hi' + person); } function doMorningWork() { var person = '老爸'; function mettAuntie() { var person = '漂亮阿姨'; console.log('hello~ ' + person); } sayHi(); // mettAuntie(); } doMorningWork(); // hi 老媽 ``` :bulb: **範例二** : sayHi( )隱藏,mettAuntie( )打開 1. 此時 meetAuntie()的 person 會指向 '漂亮阿姨',因為自己本身就有宣告這個變數。 2. 同上一個情境,若meetAuntie() 沒有宣告 person ,此時 person就會指向外面一層的 '老爸',再若是沒有找到,最後就會指向全域宣告的 person = '老媽'。 ## :+1: 相關參考文件 :::info [執行環境與堆疊](https://medium.com/@yining1204/javascript-%E6%A0%B8%E5%BF%83%E7%AF%87-%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-chap-9-%E5%9F%B7%E8%A1%8C%E7%92%B0%E5%A2%83%E8%88%87%E5%9F%B7%E8%A1%8C%E5%A0%86%E7%96%8A-ca7c5b4ca17c) [JavaScript核心篇系列文](https://ithelp.ithome.com.tw/articles/10213660) ::: ## :memo: 學習回顧 :::info * 編譯式語言 : 編譯時可除錯,效能較好。 * 直譯式語言 : 執行前沒有經過編譯。 彈性高,不須預先定義型別。 * 執行的錯誤情境 * LHS : 用來賦予值至左側變數上。 * RHS : 取值來自右邊的變數。 * 語法作用域 * 靜態作用域 : 語法解析時就已確定作用域,且不會再改變。 * 動態作用域 : 作用域在函式調用時才決定。 * JS 作用域在函式裡面 : 是一層一層向內的 (外圍是全域作用域) (內層都是由 function( ) 所包著)。 * 執行環境 : 每執行一次便會產生一個新的執行環境,this 會隨著執行環境不同而改變。 * 執行環境的生成與堆疊 (Execution Stack) 與函式的宣告順序無關,而是與呼叫的順序相關聯。 * 範圍鍊是依據函式文法本身來決定,與執行環境無關。 ::: <style> .red { color: red; } .green { color: green; } </style>