Handsome Man 怎可能不是我,這一定是有什麼誤會…..
當我們宣告變數並取用時,有時候會發生 undefined 、 is not defined 或是取得的值跟想像中的不一樣,囧:
1 | var HandsomeMan = '我'; |
……,Handsome Man 怎會是 undefined,Handsome Man 是我啊!!!
1 | console.log(HandsomeMan); |
1 | console.log(HandsomeMan); |
此時就要考慮到是不是 hoisting 的關係。接下來我們將了解變數在宣告時會怎樣去執行。
創造環境與執行
在執行環境與執行堆疊這篇文章有講過,我們去執行程式碼時會建立執行環境,而執行環境的建立其實是有步驟的,首先,會先創造環境,變數在宣告時,會先將變數放到記憶體key
的位置,如下圖 2 的 a、b、c 變數,但在這個階段還不會賦予值給它,在創造環境時就先在記憶體佔了一個位置,一直等到我們執行時才會賦予值,而這個流程我們稱為 提升(hoisting)。
因此,我們可以將得到 undefined
結果的原始碼執行拆解為下列的步驟,在創造階段先於記憶體內建立 HandsomeMan
這個 key
的位置,然後我們就先執行 console.log(HandsomeMan)
,但此時雖然已經將變數帶入到記憶體的 key
中,但是並沒有賦予它值,所以會得到 undefined
的結果。
1 | console.log(HandsomeMan); |
至於,結果為 is not defined
,是根本沒有宣告變數,所以會有這樣的結果。
函式陳述式在創造階段優先載入
除了變數以外,我們在使用函式陳述式宣告變數時其優先權是不同的,從圖一可以看到函式陳述式在創造階段時就會優先載入進去記憶體,這個階段函式就已經可以完整的運行,下面我們舉一個範例來說明。
在函式原始碼前執行函式
當調用callName()
是在函式原始碼前面時,由於函式在創造階段就已經將完整函式載入,所以載前面調用也可以完整地去執行。
1 | callName(); // 悟空 |
函式陳述式與函式表達式在 Hoisting 的差異
函式陳述式與函式表達式雖然都可以調用函式執行功能,但他們在建立時是有差異的,從範例中我們可以看到callName()
有兩次調用,在第一次調用時,函式陳述式已經優先在創造階段完整載入,而函式表達式在創造階段雖然宣告callName
變數,但這邊變數在重複宣告時是沒有用的,所以第一次的調用會得到悟空
的結果,一直到了第二次調用時,函式表達式的變數已經賦予值,所以得到悟飯
的結果。
1 | callName(); |
最後再來一個範例,在原始碼中兩個名稱相同函式陳述式後方都調用此函式,得到的結果都是哇沙米 🤮,這邊我們同樣利用創造階段與執行階段來區分,事實上在創造階段時,兩個函式都已經完整載入,但後面會覆蓋掉前面的,然後在執行階段執行了兩次函式都會是想吃哇沙米這個函式 🤮。
1 | function eatFood() { |
總結
記得變數與函式有 Hoisting 的特性,其流程可以分為創造階段及執行階段,函式在創造階段就會優先完整載入。