iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0
Modern Web

JavaScript學習日記系列 第 9

JavaScript學習日記 : Day9 - 執行環境(Execution Context)

執行環境就是當前Javascript代碼被執行時所在的環境,Javascript在運行任何代碼都是在執行環境運行,在執行環境的創建階段以下三個都會被建立:

  1. 變數對象(variable)
  2. 作用域鏈(scope chain)
  3. this

1.執行環境類型

  1. 全域執行環境 : 不在任何函數內的代碼,全都位於全域執行環境。他做了兩件事,一是創建全域對象,在瀏覽器中就是window對象,二是將this指向這個全局對象,一個程序中,只能存在一個全域執行環境。

  2. 函數執行環境 : 每次呼叫函數時,都會為該函數創建一個新的執行環境。每個函數都有獨立的執行環境,但是只有函數被調用時才會創建。

2. 執行環境生命週期

創建階段 -> 執行階段 -> 回收階段

  • 創建階段
    函數被調用,在運行內部代碼之前,會做以下三件事 :
  1. 創建變數對象 : 首先初始化函數的參數arguments,提升函數聲明(聲明表達式)和變數聲明(變數聲明提前僅限var聲明)
  2. 創建作用域鏈 : 作用域鏈是在變數對象後創建的,主要用來解析變數。JavaScript會從代碼的最內層開始,內層沒有就往上一層找,直到找到變數。
  3. 確定this指向。
  • 執行階段
    創建後,就開始執行代碼,會完成賦值(創建階段有創建變數了)

  • 回收階段
    函數調用完畢後,等待垃圾回收器回收執行環境。

用一個例子來說明:

let a = 10
function out() {
    let b = 20;
    function inner() {
        let c = 30;
        console.log(a+b+c);
    }
    inner();
}
out();

上面的代碼會歷經底下過程:

  1. 當代碼開始執行,創建全域環境。
  2. 開始執行全域環境的代碼,進行賦值、函數調用等操作。執行到out()時,out()函數創建自己的執行環境。
  3. 開始執行out()函數內的代碼,進行賦值、函數調用,執行到inner()時,inner()創建自己的執行環境。
  4. 開始執行inner()函數內的代碼,進行賦值、函數調用,因為裡面沒有可以生成其他執行環境的需求,所以inner()執行完畢後,inner執行環境回收。
  5. 回到out()執行環境,繼續執行剩餘的代碼,此例子沒有代碼,所以out()執行環境也回收。
  6. 回到全域執行環境,直到瀏覽器關閉,全域執行環境回收。

其中每個執行環境都是依序進入call stack中,執行完畢回收後就會離開call stack。

2.1 變數對象(Variable Object,VO)

變數對象(VO)是一個類似容器的對象,與作用域、作用域鏈息息相關。

變數對象的創建過程三個規則 :

  1. 建立argument對象,建立該對象下的屬性與屬性值。
  2. 檢查執行環境中的函數聲明,也就是用function關鍵字聲明的函數,在變數對象中以函數名建立一個屬性,屬性值指向該函數所在記憶體地址的引用。如果該屬性之前已經存在,該屬性將被新的引用所覆蓋。
  3. 檢查執行環境中的變數聲明,每找到一個變數,就在變數對象中以變數名建立一個屬性,屬性值為undefined。如果該變數名的屬性已經存在,為了防止同名的函數被修改為undefined,則會跳過,原屬性值不會被修改。

關於第三點舉個例子:

function test() {
    function inner() {
        console.log("inner");
    }
    var inner = 123

    console.log(inner)
}

test() // 123

按照第三點的規則,inner屬性名稱已經聲明過了,所以底下var所聲明的是會跳過的,那為什麼執行結果最後還是123呢?因為這三條規則只有在變數對象的創建時適用,而賦值123是發生在執行階段,所以結果自然是123,這種現象很讓人頭疼,其實也是因為var聲明的變數允許重複命名所導致的,若使用let來聲明就可以避免這種狀況(上面的例子改為let,則會出現'func' has already been declared)。

執行環境還沒進入執行階段時,變數對象中的屬性都不能被訪問。但是進入執行階段,激活為活動對象就可以被訪問,然後開始執行執行階段的操作。

3. 作用域練

定義 : 多個執行環境(作用域)的變數對象串聯起來組成的鏈表就是作用域鏈。

作用域可以比擬為蒸籠,最底下一層為全局AO,裡面的蒸汽(變數與函數的可見性),可以滲透整個蒸籠,底層之上的其他蒸籠層的蒸氣只能往上,也就是只能影響上面的蒸籠層。

作用域的頂端一定是當前作用域(local scope)對應的變數對象,底端一定是全局作用域對應的變數對象(全局VO)。

根據以上的了解,查找變數與函數時JS引擎會從離它最近的作用域開始查找,也就是離它最近的變數對象(VO)開始查找,找不到就往上一層VO直到全域找到為止。


上一篇
JavaScript學習日記 : Day8 - 作用域(Scope)
下一篇
JavaScript學習日記 : Day10 - This
系列文
JavaScript學習日記30

尚未有邦友留言

立即登入留言