📓
Something About JavaScript
  • Introduction
  • Day 1:前言
  • Day 2:資料型態的夢魘——動態型別加弱型別(1)
  • Day 3:資料型態的夢魘——動態型別加弱型別(2)
  • Day 4:動態型別加弱型別不是罪——怎麼 JavaScript 一摔就變成個印度阿三?
  • Day 5:湯姆克魯斯與唐家霸王槍——變數的作用域(Scope) (1)
  • Day 6:湯姆克魯斯與唐家霸王槍——變數的作用域(Scope) (2)
  • Day 7:傳統 var 關鍵字的不足
  • Day 8:var 掰掰 —— ES6 更嚴謹安全的 let 和 const
  • Day 9:圖解變數作用域(Scope)
  • Day 10:程式也懂電梯向上? —— Hoisting
  • Day 11:Strict Mode (嚴謹模式)
  • Day 12:看 Strict Mode 如何施展「還我漂亮拳」(1)
  • Day 13:看 Strict Mode 如何施展「還我漂亮拳」(2)
  • Day 14:來挖挖恐龍骨 —— with 語法
  • Day 15:this 關鍵字 (1)
  • Day 16:this 關鍵字 (2)
  • Day 17:this 關鍵字 (3)
  • Day 18:this 關鍵字 (4)
  • Day 19:函數定義 (Function Definition) 的 100 種寫法
  • Day 20:ES6 的箭頭函數 (Arrow Functions)
  • Day 21:箭頭函數 (Arrow Functions) 的 this 和你想的不一樣 (1)
  • Day 22:箭頭函數 (Arrow Functions) 的 this 和你想的不一樣 (2)
  • Day 23:ES6 物件實字威力加強版 (Enhanced Object Literals)
  • Day 24:函數呼叫 (Function Invocation) 與立即函數 (Self-Invoking Functions)
  • Day 25:不是多了塊魚 —— 立即函數的應用整理
  • Day 26:程式界的哈姆雷特 —— Pass by value, or Pass by reference?
  • Day 27:別管變數 Pass by Whatever,尋找容易理解的銀色子彈 (Silver Bullet)
  • Day 28:閉包 (Closures)
  • Day 29:閉包 (Closures) 進階打怪實戰
  • Day 30:ES10 醞釀中 —— 擁抱 JS の 未來
Powered by GitBook
On this page
  • JavaScript 裡的文章置頂效果 —— Hoisting
  • Hoisting 置頂了宣告的效果
  • 變數宣告的 Hoisting 效果 (透過 var 關鍵字)
  • 變數的 Hoisting 效果只有「宣告」的部分,不包含「初始化」(Initializations)
  • 函數也有 Hoisting 效果 (透過 function 關鍵字)
  • 透過變數方式宣告的函數,Hoisting 效果比照變數
  • 使用 let 或 const 宣告的變數不具備 Hoisting 效果
  • 補充:真相是 let 和 const 其實也有 Hoisting
  • 總結 Hoisting
  • References

Was this helpful?

Day 10:程式也懂電梯向上? —— Hoisting

PreviousDay 9:圖解變數作用域(Scope)NextDay 11:Strict Mode (嚴謹模式)

Last updated 4 years ago

Was this helpful?

有沒有注意過 JavaScript 裡一個神奇的現象?

比如以下程式:

console.log(x);

執行結果:

Uncaught ReferenceError: x is not defined

(Source: )

因為使用了根本不存在的變數 x,執行時拋出錯誤,非常合理。

為了彌補,我們趕快增加 x 的變數宣告:

console.log(x);
var x = "OneJar";

你程式老師在你後面,他非常火。

(Source: )

變數宣告當然是要在使用之前,補在後面有什麼用,一樣會先執行到拋錯的那一行。

執行結果:

undefined

(Source: )

Why?為什麼宣告在使用之後不會拋錯?而且印了個莫名的答案。

我知道了。

喂,冷靜。

程式不會變魔術,一定有理可循。

其實背後的原因,就是今天文章要介紹的 Hoisting。

JavaScript 裡的文章置頂效果 —— Hoisting

我個人偏好翻成「宣告置頂」。

就像生活中網路論壇常見的置頂文章,當一些文章被設定為置頂文,無論你進入討論版的下一步想做什麼,這些置頂文都會優先被看到。

Hoisting 的效果非常類似這樣的概念。

Hoisting 置頂了宣告的效果

以下是 W3Schools 裡的介紹:

Hoisting is JavaScript's default behavior of moving all declarations to the top of the current scope.

Hoisting 是 JavaScript 的預設行為,把所有宣告效果提到當前 Scope 的頂端。

也就是說,在正式執行程式之前,JavaScript 會先偷跑一個動作——把程式碼中宣告的部分提前到所屬 Scope 的頂端。

變數宣告的 Hoisting 效果 (透過 var 關鍵字)

例如文章開頭舉的例子:

console.log(x);
var x = "OneJar";

運作上等同於:

var x;
console.log(x);
x = "OneJar";

這就是為什麼 JavaScript 變數可以在宣告之前就使用,而不會拋錯。

但我的程式碼明明是 var x = "OneJar";,宣告同時就給予初始值,為何印出來的結果是 undefined 而非 "OneJar"?

變數的 Hoisting 效果只有「宣告」的部分,不包含「初始化」(Initializations)

再引用 W3Schools 裡的原文:

JavaScript Initializations are Not Hoisted.

也就是說,只有宣告的部分會被提升。

這個觀念不那麼直觀,因為不是以一整行的程式碼來看,而是單獨抽出了程式碼中「宣告」的部分。

以 var x = "OneJar"; 這行程式來說,裡面包含 2 個動作: 1. var x:宣告一個變數名叫 x。 2. x = "OneJar":將賦值給變數 x (撰寫時將宣告和賦值寫在同一行,這個動作也稱為「初始化」)。

Hoisting 的效果只涵蓋動作 1,不涵蓋動作 2。

所以這段程式:

console.log(x);
var x = "OneJar";

經過 Hoisting 後等同於:

var x;
console.log(x);
x = "OneJar";

而非:

var x = "OneJar";
console.log(x);

函數也有 Hoisting 效果 (透過 function 關鍵字)

以下這種寫法一定不陌生:

sayHi();

function sayHi(){
    console.log('Hi');
}

執行結果:

Hi

是否曾經覺得奇怪,為什麼函數 sayHi() 宣告定義在後面,卻可以提前呼叫?

這也是 Hoisting 效果。

使用 function 關鍵字去宣告函數,整個函數定義都會被提到 Scope 最前面。

function sayHi(){
    console.log('Hi');
}

sayHi();

透過變數方式宣告的函數,Hoisting 效果比照變數

定義函數除了直接使用 function 做宣告和定義,也允許用「var 變數宣告 + function 函數定義」的寫法。

但需要注意的是,就像前面提到:Hoisting 效果只有「宣告」的部分,不包含「初始化」。

例如以下例子:

console.log( sayHi );
console.log( sayHi() );

var sayHi = function(){
  return "Hi";
};

執行結果:

undefined
Uncaught TypeError: sayHi is not a function

被提升的只有「var 變數宣告」的部分,「function 函數定義」的部分仍在原本的位置。

運作上的效果就像以下程式碼:

var sayHi;

console.log( sayHi );
console.log( sayHi() );

sayHi = function(){
  return "Hi";
};

使用 let 或 const 宣告的變數不具備 Hoisting 效果

W3Schools:

Variables and constants declared with let or const are not hoisted!

Day8 文章介紹到 ES6 導入新的變數宣告關鍵字:let 和 const。

需要注意到,這兩個關鍵字所宣告的變數不會有 Hoisting 效果。

console.log(x);
let x = "OneJar";

執行結果:

Uncaught ReferenceError: x is not defined

補充:真相是 let 和 const 其實也有 Hoisting

例如以下範例:

let x = "OneJar";

function test(){
  console.log(x);
  let x;
}
test();

如果沒有 hoisting,理論上應該會根據 Scope Chain 找到外面的 x,印出 "OneJar"。

但實際上的執行結果:

Uncaught ReferenceError: x is not defined

原因簡單來說,節錄文章的一句話:

let 與 const 也有 hoisting 但沒有初始化為 undefined,而且在賦值之前試圖取值會發生錯誤。

文章作者花了很多篇幅講解 hoisting 背後的運作原理,方知小小的 hoisting 觀念要深入,細節也是無窮無盡。

總結 Hoisting

Hoisting 效果包含: 1. 使用 var 的變數宣告。 2. 使用 function 宣告的函數與其定義。

Hoisting 效果不包含: 1. 初始化的部分 (Initializations),例如變數初始值或使用 var 宣告的函數定義。 2. 使用 let 或 const 的變數宣告。 (實際上有,但行為和 var 不一樣)

References

(Source: )

Hoisting 這個術語在 裡翻譯為「提升」,但我覺得這個名詞太抽象,概念上不易理解。

感謝邦友 提供一篇文章——,才了解其實 let 和 const 有 hoisting,只是行為不一樣。

MDN
Caesar
我知道你懂 hoisting,可是你了解到多深?
提升(Hoisting) - 術語表| MDN
W3Schools - JavaScript Hoisting
我知道你懂 hoisting,可是你了解到多深?
白爛貓貼圖
Youtube
白爛貓貼圖
網路