# Day 10：程式也懂電梯向上？ —— Hoisting

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

比如以下程式：

```javascript
console.log(x);
```

執行結果：

```
Uncaught ReferenceError: x is not defined
```

![](https://i.imgur.com/zhRQGS8.png)\
(Source: [白爛貓貼圖](https://store.line.me/stickershop/product/1265214/?ref=Desktop))

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

為了彌補，我們趕快增加 `x` 的變數宣告：

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

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

![](https://i.imgur.com/8IUOmvs.png)\
(Source: [Youtube](https://www.youtube.com/watch?v=RrzNpZcoaPs))

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

執行結果：

```
undefined
```

![](https://i.imgur.com/IAgBBAs.png)\
(Source: [白爛貓貼圖](https://store.line.me/stickershop/product/4337259/?ref=Desktop))

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

我知道了。

![](https://i.imgur.com/Lb9iqZ9.jpg)\
(Source: [網路](https://farm9.static.flickr.com/8716/28345276131_48339620bf_o.jpg))

喂，冷靜。

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

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

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

Hoisting 這個術語在 [MDN](https://developer.mozilla.org/zh-TW/docs/Glossary/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` 關鍵字)

例如文章開頭舉的例子：

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

運作上等同於：

```javascript
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。**

所以這段程式：

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

經過 Hoisting 後等同於：

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

而非：

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

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

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

```javascript
sayHi();

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

執行結果：

```
Hi
```

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

這也是 Hoisting 效果。

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

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

sayHi();
```

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

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

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

例如以下例子：

```javascript
console.log( sayHi );
console.log( sayHi() );

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

執行結果：

```
undefined
Uncaught TypeError: sayHi is not a function
```

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

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

```javascript
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 效果。

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

執行結果：

```
Uncaught ReferenceError: x is not defined
```

### 補充：真相是 `let` 和 `const` 其實也有 Hoisting

感謝邦友 [Caesar](https://ithelp.ithome.com.tw/users/20113117/profile) 提供一篇文章——[我知道你懂 hoisting，可是你了解到多深？](https://blog.techbridge.cc/2018/11/10/javascript-hoisting/?fbclid=IwAR1bZBRYodHNbq3Xo22HsnRoR-uiJEfnXFJmtdR1fKGAutaTqh8FOCQmONo)，才了解其實 `let` 和 `const` 有 hoisting，只是行為不一樣。

例如以下範例：

```javascript
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

* [提升（Hoisting） - 術語表| MDN](https://developer.mozilla.org/zh-TW/docs/Glossary/Hoisting)
* [W3Schools - JavaScript Hoisting](https://www.w3schools.com/js/js_hoisting.asp)
* [我知道你懂 hoisting，可是你了解到多深？](https://blog.techbridge.cc/2018/11/10/javascript-hoisting/?fbclid=IwAR1bZBRYodHNbq3Xo22HsnRoR-uiJEfnXFJmtdR1fKGAutaTqh8FOCQmONo)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://something-about-js-book.onejar99.com/day10.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
