Javascript執行環境 (Execution Context)簡介

Javascript是目前Web Application developers最常使用的語言之一,許多前端框架的出現降低了前端開發的門檻也吸引了許多開發人員躍躍欲試。Javascript看似容易入門,但若把它當成Java、C#等靜態語言寫有可能會遇到預料之外的bug。這裡將我最近學到的執行環境相關知識做個分享,若有寫錯的地方也想請大家留言幫助我釐清觀念。

全域執行環境 (Global Execution Context)

Javascript Engine開始執行JS code時會創造一個全域執行環境並包含了下列內容

  • global object
  • this指標
  • your code

global object會因為執行端而有所不同,但在全域執行環境的this指標皆會指向global object。若是使用browser執行Javascript程式碼,全域執行環境的global object是開發前端很常見的window物件,現在就來打開browser的developer tool驗證吧,在console輸入 this 或是 console.log(this)會顯示window物件。

在全域執行環境宣告的variable或function會成為global object的property,可以在console宣告一個變數並輸出window物件看看。

如果是使用nodeJS或是其他執行端則global object就不是window物件了。假如你使用的JS library與window物件有緊密相依,那它很有可能只能透過browser使用。

執行環境與範圍鍊 (scope chain)

既然有全域執行環境這個名詞,那想必有不是全域的執行環境。創造其他執行環境的方法就是執行function。我們先來看個簡單的例子 ,讀者可以先想想看console輸出的結果跟你所想的一不一樣 :


想完了嗎 ? 那我們來公布答案吧

global before invoking func:g
a(): a
b(): g
global after invoking func:g

跟你想的一樣嗎 ? 我們來一一列出觀察到的事實 :

function內定義相同名字的變數不會影響到全域執行環境的變數

我們可以觀察到在 a() 裡我們重複定義了 var myVar = 'a' ,但是執行完 a() 後我們看到myVar的值依然是g,這是因為執行function時Javascript Engine創造了新的執行環境 a() 定義的myVar可視為是另一個存在於不同執行環境的變數,所以不會影響到原本在全域執行環境定義的myVar。

function b()沒有定義myVar卻沒有 Uncaught ReferenceError: myVar is not defined的例外錯誤

我們可以觀察到b() 所印出的myVar是在全域執行環境定義的,這是因為執行環境擁有外部環境的reference,如果執行環境找不到使用的變數則會往外部環境一層一層去尋找。各個執行環境的外部連結串在一起形成一條範圍鍊。

以上面的demo code為例子,b() 在執行code時在自己的執行環境找不到myVar因此向外部環境尋找,也就是全域執行環境。值得注意的是b() 雖然是在a() 執行的,但是b() 的外部執行環境並不是a() 。因為定義b() 的環境是在全域執行環境,讀者可以試試看把定義b() 的函數定義(function definition)放進a() 看看會不會有不一樣的結果。

提升(Hoisting)

Javascript Engine在創造執行環境會分成兩階段:

  1. 創造階段(creation phase)
  2. 執行階段(execution phase)

在創造階段Javascript Engine會設定this指標、variable的記憶體位址與function object,若是全域執行環境還會再創造global object。在執行階段則是執行撰寫的Javascript程式碼。我們再看一個簡單的例子,讀者也來猜猜看output是什麼

答案是 :

undefined
Hi Eric

var a在使用後才定義且值是undefined

這也是一個和靜態語言很不一樣的地方。第1行並沒有出現 Uncaught ReferenceError,而是出現undefined。原因是在創造階段時Javascript Engine已經將a的記憶體位址設定好了,只是在執行階段時的第9行才真正assign “I”m a”給它。

undefined的意思是變數已經有記憶體位址,但是還不知道它的值。因此盡量不要將undefined assign給任何變數。

sayHi可以正常執行

我們也觀察到sayHi在執行時並不是undefined,因為在創造階段已經將function object產生了。但若function並不是用函數定義而是使用函數表示式(function expression)放入變數的行為又不一樣了。我們再看一個例子 :

執行完的output :

undefined
Uncaught TypeError: sayHi is not a function

sayHi在創造階段會被視為變數,因此是undefined

以上在創造階段對variable與function的設定就稱為Hoisting

現在Vue、React等前端框架相當盛行,也有像是Typescript等轉譯式語言讓開發者們能用更便利的方式使用Javascript,但是這些library還是使用Javascript撰寫出來的。我認為了解原生Javascript仍然是幫助很大的,可以提升開發的品質以及幫助除錯,也不會陷入被其中一種框架或library綁死的窘境。