艾草:「我們今天來提升一下吧!」
「不是每天都在提升魔力總量了嗎?」
艾草:「不一樣唷,今天的提升更特別,我特別提倡身心靈的提升。」
(艾草一說完,我發現身體開始往上升了起來。)
「咦咦,啊啊,修但幾勒,不是這種提升吧,我怕高~~~~~~嗚啊啊啊啊」
艾草:「看樣子這次的提升也會是你難忘的回憶呢!ಠﭛಠ」
is not defined
因為記憶體裡沒有變數 a
,自然會顯示找不到變數 a
。
console.log(a); //Uncaught ReferenceError: a is not defined
undefined
a
變數已宣告但尚未賦值時,undefined
為預設代入的值,表示值還沒被定義。
var a;
console.log(a);//undefined
理解了以上兩者的差異後,來想想在變數宣告前先 console.log(a)
,會跑出哪個結果呢?
console.log(a);
var a = 10;
ReferenceError: a is not defined
undefined
如果依照程式碼由上往下跑的特性,我們可能會猜測是答案 1,但正確解答為 2.undefined
!這其中的原因便是 Hoisting。
Hoisting 為 JavaScript 獨有的現象,代表變數的宣告和函式的宣告會在創造階段就被放入記憶體。
讓我們複習一下執行環境吧!
JavaScript在運行前會先建立執行環境,第一個被建立的是全域執行環境 (Global Execution Context),而每個 function
也都會有自己獨立的執行環境,會由下依序往上疊加。
那我們把環境建立好後,當然就是開始動工啦!
其實執行環境在創建後區分以下兩階段運行:
進入創造階段時,JavaScript 會開始創造 Variable Object(VO),所謂的創建 VO 指的是會先搜尋此作用領域(scope) 內擁有的變數、函式,找到後會先將這些被宣告的變數、函式儲存到記憶體,這就是提升的幕後黑手!
值得注意的點是需將變數分為宣告與賦值兩階段,僅會提升宣告階段,不提升賦值階段。
在此階段會依造程式碼一行一行去執行。
回到一開始的程式碼,console.log(a)
顯示 undefined
便是因為 a
在創造階段 JavaScript 創造 VO 時,已經被存入記憶體空間了,而 a 的值尚未被賦予 10 這個值,是因為僅會提升宣告階段,不提升賦值階段。
小結:JavaScript 會在 EC 裡創造階段時先創造 VO ,這就是 Hoisting 的原因。
為何特別提到變數的宣告會在編譯階段就被放入記憶體呢?
console.log(a);
var a = 10;
我們可能會以為結果會是 10,但結果卻是 undefined
。
那是因為在創造時會將已被宣告的變數先建立新的記憶體空間,但跑程式碼的順序還是由上往下。
可以理解為:
var varHoistingTest; //創造時提前建立 a 變數的記憶體空間
console.log(varHoistingTest);//undefined
a = 10;
既然是變數的宣告都會在創造階段被放入記憶體那不論寫 var
、let
、const
應該都是一樣的吧?
console.log(varHoistingTest); //undefined
var varHoistingTest;
//let、const 的 Hosting 效果差不多,所以先用let來測試
console.log(letHoistingTest); //ReferenceError: letHoistingTest is not defined
let letHoistingTest;
咦!結果居然不一樣難道是 let
跟 const
沒有被提升嗎?
NONONO,事情可沒這麼簡單!
var a = 10;
function letTest(){
console.log(a)
//ReferenceError: Cannot access 'a' before initialization at letTest
let a;
}
letTest()
如果今天 let
沒有被提升的話,在查詢時應該會果斷回覆 10,但卻顯示了還沒執行到 a
之前不能使用這個變數!
其實變數 var
、let
、const
都是有被提升的,差別在 var
預設會初始化一個值 undefined
,而 let
、const
並沒有這個設定,且在初始化前它們不能夠被使用,如果被使用會導致 Temporal Dead Zone(TDZ) 時間死區。
小結:只要是變數都有被提升,差別在 var
會自動初始化並賦予值 underfind
,但 let
、const
不會,在未對它們進行初始化前強行使用會導致時間死區TDZ。
這裡複習一下定義函式的方式:
函式表達式 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;
}
帶入在函式陳述式裡的參數也會有提升的效果。
小結:函式陳述式及函式參數有提升的效果,但函式表達式沒有。
現在我們已經知道變數、函式宣告都會有提升的效果了,那它們是誰優先呢?
變數 PK 函式
console.log(hoistingFirst);
var hoistingFirst = 123456789;
function hoistingFirst() {};
檢查時會回傳的是 function
而不是 undefined
,結果為 function
獲勝!
var
宣告的變數,會先初始化被賦予undefined
let
、const
宣告的變數,提前取用會導致時間死區TDZhttps://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