iT邦幫忙

2021 iThome 鐵人賽

DAY 23
3

前情提要

艾草:「我們今天來提升一下吧!」

「不是每天都在提升魔力總量了嗎?」

艾草:「不一樣唷,今天的提升更特別,我特別提倡身心靈的提升。」

(艾草一說完,我發現身體開始往上升了起來。)

「咦咦,啊啊,修但幾勒,不是這種提升吧,我怕高~~~~~~嗚啊啊啊啊」

艾草:「看樣子這次的提升也會是你難忘的回憶呢!ಠﭛಠ」


前言

ReferenceError: __ is not defined、undefined 差異

is not defined

因為記憶體裡沒有變數 a,自然會顯示找不到變數 a

console.log(a); //Uncaught ReferenceError: a is not defined

undefined

a 變數已宣告但尚未賦值時,undefined 為預設代入的值,表示值還沒被定義。

var a;  
console.log(a);//undefined

Hoisting

理解了以上兩者的差異後,來想想在變數宣告前先 console.log(a),會跑出哪個結果呢?

console.log(a);
var a = 10;  
  1. ReferenceError: a is not defined
  2. undefined

如果依照程式碼由上往下跑的特性,我們可能會猜測是答案 1,但正確解答為 2.undefined!這其中的原因便是 Hoisting。

Hoisting 為 JavaScript 獨有的現象,代表變數的宣告和函式的宣告會在創造階段就被放入記憶體。

讓我們複習一下執行環境吧!


執行環境 Execution Context:

JavaScript在運行前會先建立執行環境,第一個被建立的是全域執行環境 (Global Execution Context),而每個 function 也都會有自己獨立的執行環境,會由下依序往上疊加。

那我們把環境建立好後,當然就是開始動工啦!

其實執行環境在創建後區分以下兩階段運行:

第一階段:創造階段 (Creation Phase)

進入創造階段時,JavaScript 會開始創造 Variable Object(VO),所謂的創建 VO 指的是會先搜尋此作用領域(scope) 內擁有的變數、函式,找到後會先將這些被宣告的變數、函式儲存到記憶體,這就是提升的幕後黑手!

值得注意的點是需將變數分為宣告與賦值兩階段,僅會提升宣告階段,不提升賦值階段。

第二階段:執行階段 (Execute Phase)

在此階段會依造程式碼一行一行去執行。

回到一開始的程式碼,console.log(a) 顯示 undefined 便是因為 a 在創造階段 JavaScript 創造 VO 時,已經被存入記憶體空間了,而 a 的值尚未被賦予 10 這個值,是因為僅會提升宣告階段,不提升賦值階段。

小結:JavaScript 會在 EC 裡創造階段時先創造 VO ,這就是 Hoisting 的原因。


變數宣告 Hoisting

為何特別提到變數的宣告會在編譯階段就被放入記憶體呢?

console.log(a);
var a = 10;

我們可能會以為結果會是 10,但結果卻是 undefined

那是因為在創造時會將已被宣告的變數先建立新的記憶體空間,但跑程式碼的順序還是由上往下。

可以理解為:

var varHoistingTest; //創造時提前建立 a 變數的記憶體空間
console.log(varHoistingTest);//undefined
a = 10;

既然是變數的宣告都會在創造階段被放入記憶體那不論寫 varletconst 應該都是一樣的吧?

console.log(varHoistingTest); //undefined
var varHoistingTest;

//let、const 的 Hosting 效果差不多,所以先用let來測試
console.log(letHoistingTest); //ReferenceError: letHoistingTest is not defined
let letHoistingTest;

咦!結果居然不一樣難道是 letconst 沒有被提升嗎?

NONONO,事情可沒這麼簡單!

var a = 10;
function letTest(){
  console.log(a)
//ReferenceError: Cannot access 'a' before initialization at letTest
  let a; 
}
letTest()

如果今天 let 沒有被提升的話,在查詢時應該會果斷回覆 10,但卻顯示了還沒執行到 a 之前不能使用這個變數!

其實變數 varletconst 都是有被提升的,差別在 var 預設會初始化一個值 undefined,而 letconst 並沒有這個設定,且在初始化前它們不能夠被使用,如果被使用會導致 Temporal Dead Zone(TDZ) 時間死區。

小結:只要是變數都有被提升,差別在 var 會自動初始化並賦予值 underfind,但 letconst 不會,在未對它們進行初始化前強行使用會導致時間死區TDZ。


函式 Hoisting

這裡複習一下定義函式的方式:

函式表達式 Function expression,通常會把它存成一個變數:

functionExpression(); //ReferenceError: functionExpression is not defined

var  functionTest = function functionExpression(){
	console.log("我沒有被提升");
}

可以將程式碼拆解成以下的範例,因為 VO 只提升宣告階段,所以變數已存入記憶體空間,但變數 = function 這件事尚未被執行到,所以呼叫時會顯示型態錯誤,並不是 function

var  functionTest;
console.log(functionTest);//underfind
functionTest(); //TypeError: functionTest is not a function
functionTest = function functionExpression(){
	console.log("我沒有被提升");
}

函式陳述式 Function declaration,僅陳述狀態,function 放在最前方的寫法:

functionDeclaration();

function functionDeclaration() {
  console.log('我被提升了');
}

在函式陳述式(函式宣告)時,我們即使提前呼叫程式碼,依然能執行 function 內容,函式陳述式有 Hoisting 的效果。

argumentsTest(10);    //20

function argumentsTest(number) {
  return number + number;
}

帶入在函式陳述式裡的參數也會有提升的效果。

小結:函式陳述式及函式參數有提升的效果,但函式表達式沒有。


Hoisting 優先順序大比拼

現在我們已經知道變數函式宣告都會有提升的效果了,那它們是誰優先呢?

變數 PK 函式


console.log(hoistingFirst);
var hoistingFirst = 123456789;
function hoistingFirst() {};

檢查時會回傳的是 function 而不是 undefined ,結果為 function 獲勝!


總結

  • JavaScript 會在執行環境創造階段時提前準備記憶體空間給宣告的變數、函式
  • 變數僅會提升宣告階段
  • 使用 var 宣告的變數,會先初始化被賦予undefined
  • 使用 letconst 宣告的變數,提前取用會導致時間死區TDZ
  • 函式有兩種定義方式,僅會提升函式陳述式
    • 函式表達式
    • 函式陳述式
  • 函式陳述式的提升優先於變數的提升

參考文獻

https://blog.techbridge.cc/2018/11/10/javascript-hoisting/
https://medium.com/digital-dance/javascript執行環境-execution-context-簡介-672185ed6bf4
https://blog.alexdevero.com/temporal-dead-zone-in-javascript/?ref=webdesignernews.com#declaration-and-initialization-differences-between-var-let-and-const
https://wcc723.github.io/javascript/2017/12/16/javascript-hoisting/
https://ithelp.ithome.com.tw/articles/10191549


上一篇
中階魔法 - 陳述式與表達式
下一篇
中階魔法 - Callback Function
系列文
JavaScript 魔法入門 - 從入門到中階觀念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言