JavaScript 核心篇 學習筆記: Chap.11–提升 Hoisting

執行環境其實可細分為兩個階段:創造環境及執行。

Yi-Ning
6 min readNov 9, 2019

我們可以先想像記憶體是成對的,左邊的格子表示key,而右邊是value。創造環境做的事情是,把這段程式碼中所有變數挑出來,在記憶體中分配空間給這些變數,把變數的key放進來。這個動作便稱為“提升 Hoisting”。在這個階段還不會給他值,所以如果在此時去取用這些變數的話,值會是undefined。

一直到執行時,才會把值給塞進去。

var ming = ‘小明’;

以實際的程式碼來舉例,上面這行程式碼,其實可以被拆解為兩個階段:

var ming; // 創造環境ming = ‘小明’; // 執行

函式陳述式在創造環境階段會被優先載入

不過,函式又跟一般變數不太一樣,函式陳述式在創造環境階段會被優先載入,記憶體在這個階段就會有函式的完整內容。所以函式在創造環境階段就已經可以被運行。

所以就算像下面的範例這樣寫,也可以順利得到我們期望的結果。

// 執行 
callName();
// 創造階段
function callName() {
console.log(‘呼叫小明’);
}

那如果是先宣告一個變數,再將函式表達式賦值給他呢?

callName();var callName = function () { 
console.log(‘呼叫小明’);
}

會得到下面這個錯誤訊息:callName is not a function。callName這個時候仍是undefined。

這是因為這段程式碼其實被拆解成:

callName();// 創造階段 
var callName;
// 執行
callName = function () {
console.log(‘呼叫小明’);
}

在創造階段時,函式優先

先想一想下面兩段程式碼分別會印出什麼結果。

程式碼一:

function callName() { 
console.log(‘呼叫小明 1’);
}

var callName = function () {
console.log(‘呼叫小明 2’);
}

callName();

程式碼二:

var callName = function () { 
console.log(‘呼叫小明 2’);
}
function callName() {
console.log(‘呼叫小明 1’);
}

callName();

無論放在前面的是function callName, 或是 var callName, 印出的結果都是: 呼叫小明 2。這是因為在創造階段時 — 函式優先。上面兩個範例,如果拆解成兩個階段來看,其實都會是下面的步驟:

step 1: 函式callName被往前移,在創造階段時函式callName先被分配到記憶體中的一個位置,且函式的陳述式已被載入。

step 2: 接著才是宣告變數callName。

step 3: 進入創造階段,callName的值被新的函式陳述式覆蓋掉,因此結果會是呼叫小明2。

再來練習一個例子:

callName();function callName() { 
console.log(Ming);
}

var Ming = ‘小明’;

同樣的,把這段程式碼拆解成創造及執行兩個階段來幫助我們理解:

// 創造 
function callName() {
console.log(Ming);
}
var Ming;
// 執行
callName();
Ming = ‘小明’;

在執行callName時,他會向外尋找全域變數Ming,這時候他的值仍是undefined。因此console.log的結果會是undefined。

第三個練習例子,兩個名稱一樣的函式,這兩次執行分別會印出什麼?

function callName() { 
console.log(‘小明’);
}
callName();
function callName() {
console.log(‘杰倫’);
}
callName();

結果兩次都是杰倫,原因可以透過分解成創造及執行兩階段來理解:

// 創造 
function callName() {
console.log(‘小明’);
}
function callName() {
console.log(‘杰倫’);
}

// 執行
callName();
// 第一次執行
callName();
// 第二次執行

練習四:

whichName();function whichName() { 
if (name) {
name = ‘杰倫’;
}
}

var name = ‘小明’;
console.log(name);

最後console.log的結果是小明,原因可以透過分解成創造及執行兩階段來理解:

// 創造function whichName() { 
if (name) {
name = ‘杰倫’;
}
}
var name;
// a.函式 whichName()因函式優先被提升到最上方。
// b.宣告一個全域變數name,此時尚未被賦於值(會顯示undifined)
// 執行whichName();
name = ‘小明’;
console.log(name);
// a.呼叫whichName();在進入if判斷式之前,並沒有宣告變數name,因此向外尋找,此時全域變數name還未被賦予值,if判斷結果為false,函式執行中斷並跳出。
// b.將'小明'賦值給變數name。

--

--