もう一度JavaScriptを理解する part1

# 初めに
たくさんの資料調べるうちに数か月前自分の書いた文章を振り返ると、自分の理解があやふやになっていることに気づき、基礎概念ともう一度向き合って文章にしたいと思います。

シリーズ参考資料はこちらです↓
[You Don't Know JS (book series)](https://github.com/KBPsystem777/You-Dont-Know-JS#you-dont-know-js-book-series)

今回扱うのは
[You Don't Know JS: Up & Going - Chapter 2: Into JavaScript](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/up%20%26%20going/ch2.md)
[You Don't Know JS: Scope & Closures - Chapter 4: Hoisting](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/scope%20%26%20closures/ch4.md)
[Kinds of Emptiness](https://frontendmasters.com/courses/deep-javascript-v3/kinds-of-emptiness/)
の勉強メモです。

07/04/22編集：
元の文章では`undefined`を状態として理解していましたが、正しくは**値**です。

# Hoisting
## var
**Hoisting**は、`var`などを使って変数宣言するとき、その変数が自分のいるスコープ一番上に巻き上げていくことです。

```jsx: var hoisting example1
console.log(a) // undefined
// undefined means no value
// we declared it but we doesn't assign any value

var a // compile-time
a = 2 // run-time
console.log(a) // 2
```
**`var`を使えば`compile-time(parsing)`でのhoistingが必ず発生します。
しかしhoisitingの対象は変数です。値ではありません。
変数が`run-time(executing)`で値を付与されるまで`undefined`（値）で初期化されている。（initialize as undefined）**

`example1`実際の順序は、
`var a`　←　compile-timeでは順序に関わらず、一番上に置くのが**hoisiting**です。
`console.log(a) // undefined`
`a = 2`　←　run-timeでコードを実行するとき値を付与する。
`console.log(a) // 2`


`compile-time`というのは、コード執行する前に、JavaScriptエンジンは`Scope`で`compilation`を行ってコード解析し機械言語に変換する段階です。
同じ作者のシリーズ本ではこちらの[You Don't Know JS: Scope & Closures - 1st Edition](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/scope%20&%20closures/README.md#you-dont-know-js-scope--closures)扱っています。
少し前にスコープについて自分なりにまとめましたが、ご参考になればと。↓
[JavaScriptのクロージャについて - Scope](https://qiita.com/hu-yu/items/4e1c6253c85d1093e1d4#scope)

```jsx: var hoisting example2
foo() // undefined

function foo() {
  console.log(a)
}

console.log(a) // undefined

var a // compile-time

foo() // undefined

a = 1 // run-time

console.log(a) // 1

foo() // 1
```
`example2`の順序は、
`var a`
`function foo() {console.log(a)}` 
`foo() // undefined`　*一行目の呼び出し
`console.log(a) // undefined`
`foo() // undefined`
`a = 1`
`console.log(a) // 1`
`foo() // 1`


`function`宣言ももちろん**hoisiting**が発生します。
でもなぜ`var`の**hoisiting**が`function`より前だと言えるでしょうか？

まず`var a`を消して`foo()`だけを呼び出したら`ReferenceError: a is not defined`になります。
そして一行目の`foo()`も`console.log(a)`も`undefined`を出力することから、`var a`の後`function foo() {...}`ではないと`foo()`は実行できないはずです。（ASTにも関連している気がしますが）

それから`function foo() {...}`、`console.log(a)`...が続いていくということがわかります。そして`a = 1`run-timeで値が付与されて初めて`a`の値を出力することが可能になったんです。

（`function`はどこで書いてもトップにあがりますが、順序としては`var`のような関数宣言より下、ネストした関数（内部関数）を除いてどこで呼び出されても可能です。）

## Variable can't be hoisted in function scope?
`global scope`も`function scope`も、`var`での宣言では**hoisting**が必ず発生します。

```jsx
function outer() {
  console.log(a) // undefined
  var a
  console.log(a) // undefined
  a = 2
  console.log(a) // 2
}

outer()
```
では、なぜ下の例では、上の`test2()`が出力できなかったでしょうか...
```jsx
function outer() {
  test1() // I am test1
  // test2() // TypeError: test2 is not a function

  function test1() {
    console.log('I am test1')
  }

  var test2 = function () {
    console.log('I am test2')
  }

  test2() // I am test2
}

outer()
```
やはり**hoisting**作用の対象が誤解されやすいところだと思います。
`outer()`内部の順序、実際は
`var test2`
`function test1() {...}`
`test()1`
`test()2 // TypeError`
`test2 = function () {...}`
`test2() // I am test2`
となります。

**hoisting**は変数を巻き上げるけど、値は別の段階で付与されるから、`test2`の値（`function () {console.log('I am test2')}`）より先に呼び出されたら、
もちろん`TypeError: test2 is not a function`が出てきます。
上の`test2()`では`test2`はまだ`undefined`なので、`undefined`を関数のように扱うことができません。

## let/const
`let`と`const`には、**hoisting**が発生しないのでしょうか？
**発生します。** ここでは`TDZ(Temporal Dead Zone)`を紹介したいと思います。
`TDZ(Temporal Dead Zone)`は一時死亡状態に指します。つまり何もできない状態です。

```
// Before execution = compile time

never been created      'let/const'         'var' declared
   in any scope           declared            ex. var a
-------------------|-------------------|-------------------> time
     undeclared              TDZ              undefined
                       (uninitailized)
```

`var`宣言は変数を**hoisting**して、**execution**（run time、値が付与される）までは`undefined`で初期化になる **（initialize as undefined）** ことに対し、
`let/const`宣言も同じく変数を**hoisting**して、**execution**まで変数を`uninitialized（初期化されてない）`、何も付与できない状態を保っているんです。

`uninitialized`状態の値をアクセスしたら`ReferenceError: Cannot access before initialization`が出てきます。
そして**execution** 段階になってはじめて`undefined`という初期値が付与される。

簡潔にいうと、

`var`で変数宣言される時点（**compile time**）すぐ`undefined`という初期値がつく。**execution**で新しい値が付与されるまで`undefined`です。

`let/const`で変数宣言される時点（**compile time**）で何にもできない死亡している状態で、**execution（run time）** で初めて`undefined`が付与され、もし値を与えていなければ`undefined`になります。


```jsx
// x // ReferenceError: Cannot access 'x' before initialization

let x
console.log(x) // undefined

x = 3
console.log(x) // 3
```

# 結論

`var`, `const`, `let`の違いを理解して、変数をちゃんと宣言してから使うこと。

# ほかに
`var`、`let`、`const`以外`function`、`function*`、`class`、JavaScriptではすべての宣言が`hoisted`状態になります。
こちらにご参考にしてください。↓
[Are variables declared with let or const hoisted?](https://stackoverflow.com/questions/31219420/are-variables-declared-with-let-or-const-hoisted)