JavaScriptの巻き上げをサンプルコードで理解する

この記事は、[岩手県立大学 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ipu) 9日目の記事です。

# 1. はじめに

こんにちは。岩手大学のTakuyaHanadaです。

今回は、初Advent Calendarということで（関係はないです）、JavaScriptの**巻き上げ**についてアウトプットしていきます！

間違い等あれば、教えてくださると勉強になります。

# 2. 巻き上げとは

- `var`を使って宣言された変数
- `function`を使って宣言された関数

が、**現在のスコープ**で宣言前でも使えること。

# 3. よくわかんないから実際のコードを見て学ぶ

以下のサンプルコードの出力を予想しながら見てみてください！

※ 出力結果はnodeで動作させた場合のものです。

## 3-1. サンプルコード1

```javascript:sample1.js
var hoge = 'おはよう靴下';
console.log(hoge);
```

```bash:sample1.jsの出力
おはよう靴下
```

これは当たり前ですね。

```javascript:sample1.jsの実際の動作コード
var hoge = 'おはよう靴下';
console.log(hoge);
```

## 3-2. サンプルコード2

```javascript:sample2.js
console.log(hoge);
var hoge = 'おはよう靴下';
```

```bash:sample2.jsの出力
undefined
```

巻き上げが起こります。

はい？と思うかもしれません。これが巻き上げです。

巻き上げが起こり、`var hoge`が一番上（正確に言うと、宣言されたスコープの一番上）に来ています。

```javascript:sample2.jsの実際の動作コード
var hoge;  // 巻き上げが起こっている
console.log(hoge);
hoge = 'おはよう靴下';
```

## 3-3. サンプルコード3

```javascript:sample3.js
console.log(fuga);
let fuga = 'おはよう靴下';
```

```bash:sample3.jsの出力
ReferenceError: Cannot access 'fuga' before initialization
```

巻き上げは起こりません。

ん？と思うかもしれません。

しかし、巻き上げが起こるのは`var`で宣言した場合**のみ**です。

なので、この場合はコードの通りに動きます。（`const`の場合も`let`と同様です）

```javascript:sample3.jsの実際の動作コード
console.log(fuga);
let fuga = 'おはよう靴下';  // let で定義しているため、巻き上げが起こらない
```

## 3-4. サンプルコード4

```javascript:sample4.js
if (hoge !== 'おはよう') {
  var fuga = '靴下';

  if (fuga === '靴下') {
    var hoge = 'おはよう';
  }
}

if (hoge === 'おはよう') {
  console.log(hoge + fuga);
}
```

```bash:sample4.jsの出力
おはよう靴下
```

巻き上げが起こります。

これは、サンプルコード2と同様です。

巻き上げが起こり、`var hoge`と`var fuga`が一番上に来ています。

```javascript:sample4.jsの実際の動作コード
var hoge;  // 巻き上げが起こっている
var fuga;  // 巻き上げが起こっている
if (hoge !== 'おはよう') {
  fuga = '靴下';

  if (fuga === '靴下') {
    hoge = 'おはよう';
  }
}

if (hoge === 'おはよう') {
  console.log(hoge + fuga);
}
```

## 3-5. サンプルコード5

```javascript:sample5.js
function fn() {
  console.log('fnの中のhoge', hoge);
  var hoge = 'おはよう靴下';
}

fn();
console.log('fnの外のhoge', hoge);
```

```bash:sample5.jsの出力
fnの中のhoge undefined
ReferenceError: hoge is not defined
```

巻き上げが起こります。

このサンプルコードは、混乱するかもしれません。

しかし、巻き上げは

> `var`を使って宣言された変数が、現在のスコープで宣言前でも使えること。

なので、**現在のスコープ**（サンプルコード5の場合は`fn`）でしか使うことができません。

つまり、以下のように動いています。

```javascript:sample5.jsの実際の動作コード
function fn() {
  var hoge;  // 巻き上げが起こっている
  console.log('fnの中のhoge', hoge);
  hoge = 'おはよう靴下';
}

fn();
console.log('fnの外のhoge', hoge);
```

## 3-6. サンプルコード6

```javascript:sample6.js
fn();

function fn() {
  console.log('おはよう靴下');
}
```

```bash:sample6.jsの出力
おはよう靴下
```

巻き上げが起こります。

これは**関数**の巻き上げです。関数も`var`と同様に巻き上げが起こります。

```javascript:sample6.jsの実際の動作コード
function fn() {  // 巻き上げが起こっている
  console.log('おはよう靴下');
}
fn();
```

## 3-7. サンプルコード7

```javascript:sample7.js
fn();

const fn = () => {
  console.log('おはよう靴下');
};
```

```bash:sample7.jsの出力
ReferenceError: Cannot access 'fn' before initialization
```

巻き上げは起こりません。

これはサンプルコード3と同様です。

`const`で`fn`という**変数**を定義しているため、このようになります。

```javascript:sample7.jsの実際の動作コード
fn();

const fn = () => {  // 巻き上げは起こらない
  console.log('おはよう靴下');
};
```

## 3-8. サンプルコード8

```javascript:sample8.js
fn();

var fn = () => {
  console.log('おはよう靴下');
};
```

```bash:sample8.jsの出力
TypeError: fn is not a function
```

巻き上げが起こります。

これは、サンプルコード7と同様に`fn`という変数を定義していますが、`var`を使っているため、巻き上げが起こります。

```javascript:sample8.jsの実際の動作コード
var fn;  // 巻き上げが起こっている
fn();

fn = () => {
  console.log('おはよう靴下');
};
```

# 4. おわりに

以上がJavaScriptの巻き上げでした。

`var`, `function`を使う際には気をつけましょう。



# 5. 参考文献

- [初めてのJavaScript 第3版
――ES2015以降の最新ウェブ開発 | O'Reilly Japan](https://www.oreilly.co.jp/books/9784873117836/)