iT邦幫忙

2022 iThome 鐵人賽

DAY 30
0

今天是鐵人賽的最後一天

我們要來認識重要的 Hoisting 提升

為什麼重要呢?

認識 Hoisting 之前,在宣告變數時不會太在意變數或函式的宣告要放在哪個位置,只要結果是我們想要的就好,也不確定會造成甚麼影響。但有沒有發現函式在被宣告的程式碼前也可以正常執行,或是使用 var 出現許多奇怪的現象,造成這些現象的原因就是 Hoisting


我們一樣先從程式碼來看,以下將會用 var 來宣告變數:

var Jack = "傑克";
console.log(Jack); // 傑克

這是一個簡單的變數宣告,並且正確地印出變數的值,我們現在把 console.log 移到前面:

console.log(Jack); // undefined
var Jack = "傑克";

出現了 undefined !?
為什麼是 undefined 而不是 Jack is not defined 呢?
在使用 console.logJack 不是還沒被宣告嗎?


這時我們需要先了解宣告變數的步驟跟執行環境時做了甚麼事情

宣告變數步驟

先回到前面的程式碼,並且拆開來看:

var Jack; // 先宣告變數
Jack = "傑克"; // 再賦予值
console.log(Jack); // 傑克

上面這段程式碼在執行時會分為兩個步驟

  1. 先宣告變數 Jack
  2. 然後再把 傑克 這個值賦予到變數上

這是宣告變數時實際運作的流程

執行環境

在建立一個執行環境時會分為兩個階段,首先會先「創造環境」,接著才是「執行」。

如下圖,在創造環境這一個階段時,會先把記憶體的 key 給放上去,如圖示先把變數 a 放到記憶體的左邊,但還不會給它值。所以這時候如果去檢查變數的時候,它的值會出現 undefined

到執行階段的時候才會把值給放上去,這時檢查就會出現 1 這個值。

創造環境時會先把程式碼所有的變數都挑出來,並且先在記憶體上面把它們的空間準備好,到執行的時候才會把這些程式碼依序執行並且賦予它的值:

在創造環境把記憶體空間準備好,這個流程就稱為「Hoisting 提升」。


再回到前面的程式碼看創造環境與執行階段:

var Jack; // 創造階段
Jack = "傑克"; // 執行
console.log(Jack); // 傑克
創造階段
var Jack; // Hoisting 提升

執行階段
console.log(Jack); // undefined
Jack = "傑克"; // 此時才被賦予值

這時就比較能了解甚麼是 Hoisting 與為什麼會出現這樣的結果

函式提升

函式陳述式

函式陳述式則有些不一樣,從以下程式碼看到不管在哪裡檢查都可以印出值

callName();
function callName(){
    console.log("呼叫傑克"); // 呼叫傑克, 呼叫傑克
}

callName();

因為函式的陳述式會在創造階段就已經把它的記憶體以及它的函式都準備好了,且函式已包含所有內容。

以下一樣用創造環境與執行階段來拆解:

// 創造階段
function callName(){
    console.log("呼叫傑克"); // 呼叫傑克, 呼叫傑克
}

//執行
callName(); // 呼叫傑克
callName(); // 呼叫傑克

函式表達式

現在我們換用函式表達式來試試:

var callName = function (){
    console.log("呼叫傑克"); // 呼叫傑克
}

callName();

在後面執行當然是沒問題的

但是

callName(); // callName is not a function
var callName = function (){
    console.log("呼叫傑克"); 
}

放到前面就會發生錯誤了,檢查時會發現 callName() 會是 undefined

一樣再用創造環境與執行階段來拆解:

// 創造階段
var callName; 

// 執行
callName(); // 此時沒有值,檢查為 undefined

// 此時才把 function 賦予到變數上
callName = function (){
    console.log("呼叫傑克"); 
}

所以我們可以得知如果使用函式表達式,我們必須先等函式已經賦予到值上面,才能運行該段函式。

var 的 Hoisting 特性 與 let、const 的出現

因為 var 具有 Hoisting 特性,讓 var 有了「可以重複宣告」及「可以在使用之後才被宣告」這兩種令人頭痛的神奇特性。ES6 為了改善這些情況,於是出現了 letconst,它們最主要的差異是:

  1. letconst 是「區塊作用域」,而 var 是「函式作用域」
  2. 宣告 var 的時候,宣告會被提前至作用域的前面,這個特性就是 Hoisting。

letconst 必須要宣告後才能被使用,相較於 var 更容易理解跟閱讀,於是在 ES6 後便提倡使用 letconst 來取代 var 做宣告。但因為在維護專案或是看文章時還是可能有機會看到 var 的使用,且函式也具有 Hoisting 的特性,所以了解這些特性還是非常需要的。


參考資料

線上課程
MDN


上一篇
Day29 語法作用域 Lexical scope
系列文
從基礎開始,用 JavaScript 從頭建立起程式肌肉 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言