[JavaScript] Javascript 中的 Hoisting(提升):幫你留位子

itsems
itsems_frontend
Published in
6 min readJul 11, 2020
Photo by angela pham on Unsplash
Outline:
- Hoisting 是什麼?
- let const and Hoisting
- Temporal dead zone (TDZ)
- Quiz time!

Hoisting 是什麼?

先看一段程式碼:

console.log(a);

在尚未宣告 a 變數之下,呼叫 a 變數會得到「a 還沒被宣告」這樣的錯誤:

我們知道 Javascript 是一行一行依序執行的語言,如果我把宣告 a 變數這行加在 console 下面,照理說還沒有跑到定義那一行,又呼叫 a ,應該會出現一樣的錯誤:

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

結果卻是回傳了 undefined

如果是函式,一樣在宣告函式之前呼叫他:

name();
function name(){
return 'emma'
}

也回傳了正確的值:emma

以上的現象,就是 Hoisting,我喜歡用一句話記憶 Hoisting 的概念:「幫你留位子」,因為 Hoisting 就是 Javascript 在執行任何程式碼之前,先把宣告的變數和函式放進記憶體空間裡,就像是事先幫他們留個位子的感覺。而變數和函數的 Hoisting 略有不同,以下來看幾個 Hoisting 的特性:

1. 變數的宣告 (Declarations) 會被提升

a=1;console.log(a); // 1
var a;

在這裡我們在 console 的下面才宣告 a,但是因為宣告會被提升,在執行任何程式碼之前,Javascript 就幫他留了位子,所以其實執行起來就像是這樣:

var a;
a = 1;
console.log(a); // 1

var a 並不是真的在底層執行時被搬到程式碼的最前面,只是看起來很像是被搬到最前面

2. 變數的初始化 (Initialization) 不會被提升

初始化就是賦值的意思,看範例:

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

從這個範例就看得出來,賦值沒有被提升,要等到程式執行到這行了才會執行 a=1。

undefinedis not defined 是不一樣的錯誤,undefined 的意思是「我不知道他的值是多少」,is not defined 是「未宣告」的意思,Javascript 給已宣告未賦值的變數或是函數的預設值都是 undefined

3. 函式陳述式 (function declaration) 會被提升

callMe('emma');  // hello emmafunction callMe(name){
console.log('hello '+name);
}

函式陳述式在宣告之前就被呼叫了,並可以成功執行。

4. 函式運算式 (function expression) 不會被提升

console.log(myName);           // undefined
myName('emma'); // myName is not a function
var myName = function(name){
console.log('i am + name);
}

這種以 var 來宣告的函式,稱為函式運算式,宣告會被提升,但是函式的內容不會。

5. 函式與變數同名,函式優先

console.log(aa);  // fn a(){}var aa;
function aa(){};

如果變數和函式同名,函式將被提升,並優先權較高,並如果多個函式同名,後面的會覆蓋前面的。

6. 函式內的參數不會被提升 (函式範疇 function scope)

console.log(aa); // ReferenceError: aa is not defined
function age() {
var aa = 18;
}

在函式內以 var 宣告的變數,因為 var 本身函式範疇 (function scope) 特性的關係,可取範圍只有在函式內,不會被提升至外面。

let, const and Hoisting

W3C 中是這樣說的:

Variables and constants declared with let or const are not hoisted!
> 由 let 和 const 宣告的變數不會被提升

更精準一點說,let & const 在宣告時其實被提升,但是跟一般以 var 宣告變數的提升方式不太一樣,差別在於 let & const 提升後在未被初始化 (Initialization) 之前不可使用,意思就是 let const 宣告之後,在賦值之前,不能被取用。

暫時死區 ( Temporal dead zone )

在 let & const 宣告後未被賦值之前不可取用的期間,我們就稱為「暫時死區(Temporal dead zone)」,在這期間如果要取用 let & const 宣告的變數,Javascript 會回傳 ReferenceError 的錯誤。

console.log(name); // Uncaught ReferenceError: name is not defined
let name = 'emma';

📝 Quiz time !

以下取自 JavaScript Questions 中有關 hoisting 的小測驗,希望看完上面的說明,下面的題目可以讓你迎刃而解:

What’s the output?

function sayHi() {
console.log(name);
console.log(age);
var name = 'Lydia';
let age = 21;
}
sayHi();
  • A: Lydia and undefined
  • B: Lydia and ReferenceError
  • C: ReferenceError and 21
  • D: undefined and ReferenceError

— — — — — — — — — — — —

— — — — — — — — — — — —

— — 仔細想想,不要偷看 — -

— — — — — — — — — — — —

— — — — — — — — — — — —

Answer: D

name 這個變數在函式內獲得提升,且是由 var 所宣告,所以會回傳 undefined ,age 變數也在函式內獲得提升,但是在呼叫他的時候尚未賦值,且 age 為 let 所宣告,表示 age 仍在 TDZ 期間,故 Javascript 拋回 ReferenceError 錯誤。

答對了嗎?

內容若有任何錯誤,歡迎留言交流指教! 🐧

ref:
JavaScript Hoisting
[筆記] 談談Javascript 中的 Hoisting

延伸閱讀:
我知道你懂 hoisting,可是你了解到多深?

--

--

itsems
itsems_frontend

Stay Close to Anything that Makes You Glad You are Alive.