JavaScript核心概念 - 提升(Hoisting)

Handsome Man 怎可能不是我,這一定是有什麼誤會…..

當我們宣告變數並取用時,有時候會發生 undefined 、 is not defined 或是取得的值跟想像中的不一樣,囧:

1
2
var HandsomeMan = '我';
console.log(HandsomeMan);

Variables Hoisting
……,Handsome Man 怎會是 undefined,Handsome Man 是我啊!!!

1
2
console.log(HandsomeMan);
var HandsomeMan = '我';

Variables Hoisting

1
console.log(HandsomeMan);

Variables Hoisting
此時就要考慮到是不是 hoisting 的關係。接下來我們將了解變數在宣告時會怎樣去執行。

創造環境與執行

執行環境與執行堆疊這篇文章有講過,我們去執行程式碼時會建立執行環境,而執行環境的建立其實是有步驟的,首先,會先創造環境,變數在宣告時,會先將變數放到記憶體key的位置,如下圖 2 的 a、b、c 變數,但在這個階段還不會賦予值給它,在創造環境時就先在記憶體佔了一個位置,一直等到我們執行時才會賦予值,而這個流程我們稱為 提升(hoisting)

Variables Hoisting
Variables Hoisting
Variables Hoisting

因此,我們可以將得到 undefined 結果的原始碼執行拆解為下列的步驟,在創造階段先於記憶體內建立 HandsomeMan 這個 key 的位置,然後我們就先執行 console.log(HandsomeMan),但此時雖然已經將變數帶入到記憶體的 key 中,但是並沒有賦予它值,所以會得到 undefined 的結果。

1
2
3
4
5
6
console.log(HandsomeMan);
var HandsomeMan = '我';
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓事實上執行步驟是這樣↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
var HandsomeMan; // 創造階段
console.log(HandsomeMan); // Handsome Man 是 undefined
HandsomeMan = '我'; // 執行階段

至於,結果為 is not defined ,是根本沒有宣告變數,所以會有這樣的結果。

函式陳述式在創造階段優先載入

除了變數以外,我們在使用函式陳述式宣告變數時其優先權是不同的,從圖一可以看到函式陳述式在創造階段時就會優先載入進去記憶體,這個階段函式就已經可以完整的運行,下面我們舉一個範例來說明。

在函式原始碼前執行函式

當調用callName()是在函式原始碼前面時,由於函式在創造階段就已經將完整函式載入,所以載前面調用也可以完整地去執行。

1
2
3
4
callName(); // 悟空
function callName() {
console.log('悟空');
}

函式陳述式與函式表達式在 Hoisting 的差異

函式陳述式與函式表達式雖然都可以調用函式執行功能,但他們在建立時是有差異的,從範例中我們可以看到callName()有兩次調用,在第一次調用時,函式陳述式已經優先在創造階段完整載入,而函式表達式在創造階段雖然宣告callName變數,但這邊變數在重複宣告時是沒有用的,所以第一次的調用會得到悟空的結果,一直到了第二次調用時,函式表達式的變數已經賦予值,所以得到悟飯的結果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
callName();
var callName = function() {
// 函式表達式
console.log('悟飯');
};
function callName() {
// 函式陳述式
console.log('悟空');
}
callName();

// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓事實上執行步驟是這樣↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

//創造階段
function callName() {
// 函式陳述式
console.log('悟空');
}
var callName;
//執行階段
callName();
callName = function() {
// 函式表達式
console.log('悟飯');
};
callName();


最後再來一個範例,在原始碼中兩個名稱相同函式陳述式後方都調用此函式,得到的結果都是哇沙米 🤮,這邊我們同樣利用創造階段與執行階段來區分,事實上在創造階段時,兩個函式都已經完整載入,但後面會覆蓋掉前面的,然後在執行階段執行了兩次函式都會是想吃哇沙米這個函式 🤮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function eatFood() {
console.log('想吃沙西米');
}
eatFood();
function eatFood() {
console.log('想吃哇沙米');
}
eatFood();

// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓事實上執行步驟是這樣↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

//創造階段
function eatFood() {
console.log('想吃沙西米');
}
function eatFood() {
console.log('想吃哇沙米');
}
//執行階段
eatFood();
eatFood();

總結

記得變數與函式有 Hoisting 的特性,其流程可以分為創造階段執行階段,函式在創造階段就會優先完整載入。

0%