Javascript 執行時,必經的創造執行環境的階段,究竟底層發生什麼變化?在下列的範例中,假如我們把呼叫移到程式碼最上方,移到函式之前:
1 | b(); |
在多數程式語言裡面,這,因為還沒宣告函式就先呼叫,所以呼叫是無效的。但 JavaScript 不僅執行函式,而且沒有錯誤。
Called b!
正常印出來,代表函式 b 正常執行,但是 undefined
呢?
Javascript 程式碼在執時,不是直接被執行,而是被解析器轉換成電腦看得懂的東西。當解析器執行你的程式碼時,它會知道你在什麼位置創造變數與函數。
這個階段稱為創造 (creation),而創造階段裡面,直譯器在記憶體裡設定變數與函數的這個步驟稱為「提昇」(hoisting) 。
它不是真的把程式碼移到最上面。在逐行執行程式碼之前,JavaScript 已經替變數與函數,在記憶體裡面中建立儲存空間。所以當程式碼逐行執行時,可以找到它們。
所以函式跟它的內容(定義)會先存至記憶體,所以可以正確的呼叫,呼叫後建立這個函式的執行環境,印出 Called b!
雖然變數也在記憶體中建立空間,可是它的內容並沒有在創造階段時被放入。這邊可以把創造變數與賦值給變數當成兩件事,所以呼叫變數 a
才會印出 undefined
。
上面的程式碼,在電腦執行時可以想像成這樣的順序,結果會是一樣的:
1 | var a; |
那如果是下列狀況呢?
1 | b(); |
結果可以正常顯示
這是因為函數會先在記憶體裡創造空間,並建立它的區域變數,再執行呼叫。
可是下列的狀況雖然貌似
1 | b(); |
結果卻是
執行怎麼理解呢?可以這樣想:
變數 b
先被提升 (創造變數 b
並設定進記憶體),接著呼叫執行 b 函式,為變數 b 賦值(指向)函式。呼叫執行 b 函式時報錯,是因為變數 b 這個時候根本沒有被賦值,何況是指向函式呼叫呢?所以自然報錯 b is not a function。
可以想像成是這樣:
1 | var b; |
當然這不代表我們的 JS 程式被改寫成這樣,而只是這種貌似變數與函式被拉到執行環境最前面的現象稱為 Hoisting
重點有三:
- 變數宣告跟函式宣告都會提升
- 只有宣告會提升,賦值不會提升
- 別忘了函式裡面還有傳進來的參數
另一個小例子:
1 | var foo = 1; |
由於提昇的作用,儘管區域變數 foo 的宣告會先於呼叫 console.log 方法,也就是 區域變數的值將會作為參數傳入成為 console.log 得出的值,而不是在函數外部聲明的全域變數。
但是,由於變數宣告不會提升該值,因此輸出將是 undefined,而不是 2。
在創造階段,函數跟其函數內容(定義)會先存至記憶體,雖然變數也在記憶體中建立空間,可是它的內容並沒有在創造階段時被放入。所以呼叫變數 foo 才會印出 undefined。
延伸閱讀:
https://blog.techbridge.cc/2018/11/10/javascript-hoisting/
https://github.com/aszx87410/blog/issues/34#issuecomment-500890667