JavaScriptのthisもこれで完璧！呼び出し方で変わる5つのパターン


こんにちは、とまだです。

JavaScript アドベントカレンダー 2024 のうち、2 日目の記事をお届けします！

https://qiita.com/advent-calendar/2024/javascript

私が本格的に現場で JavaScript をはじめたとき、「`this` を理解したら中級者」と言われました。

それぐらい、`this` は JavaScript において重要な概念です。

突然ですが、以下のコードの出力結果は分かりますか？

```javascript
const user = {
  name: 'Alice',
  greet() {
    console.log(`こんにちは、${this.name}さん！`);
  },
};

const greet = user.greet;
greet(); // ???
```

どうでしょうか？
全体として、流れとしては `user.greet` を呼び出しているので、以下のように出力されると思われるかもしれません。

```
こんにちは、Aliceさん！
```

しかし、実際の出力結果は以下の通りです。

```
こんにちは、undefinedさん！
```

この結果に驚いた方も多いのではないでしょうか？

実は `this` は、どう呼び出すかによって値が変わる曲者なんです。

今回は「**JavaScript の this を完全に理解する**」ことを目指して、5 つのパターンを解説します！

## なぜ this を理解する必要があるの？

JavaScript 経験者なら、このような経験があるかもしれません。

- コードは間違ってないはずなのに `undefined` が出る
- React で `this.setState` が動かない
- イベントハンドラの中で `this` が `undefined` になる

これらの問題は、全て `this` の理解が足りないことが多いです。

「this がよく分からない」と感じている方は、ぜひこの記事を読み進めてください！

## this の基本：「誰が呼び出したか」が重要

まず、`this` は「**関数を呼び出したもの**」を指すと覚えておきましょう。

例えば、以下のようなコードを見てみましょう。

```javascript
const user = {
  name: 'Alice',
  greet() {
    console.log(`こんにちは、${this.name}さん！`);
  },
};

// userがgreetを呼び出す
user.greet(); // こんにちは、Aliceさん！

// グローバルスコープでgreetを呼び出す
const greet = user.greet;
greet(); // こんにちは、undefinedさん！
```

同じ関数なのに、呼び出し方によって結果が変わってしまいます。

なぜこうなるのか、5 つのパターンを見ながら理解していきましょう。

## this の 5 つの顔

### 1. メソッド呼び出し：オブジェクトのメソッドとして呼ぶ場合

オブジェクトのメソッドとして呼び出す場合、`this` はそのオブジェクトを指します。

```javascript
const user = {
  name: 'Alice',
  greet() {
    console.log(`こんにちは、${this.name}さん！`);
  },
};

user.greet(); // こんにちは、Aliceさん！
```

この場合、関数を呼び出したのは `user` オブジェクトなので、`this` は `user` を指します。

### 2. 普通の関数呼び出し：単独で呼び出す場合

普通の関数として呼び出す場合、`this` は `undefined`（strict モード）または `window`（非 strict モード）を指します。

```javascript
function standalone() {
  console.log(this);
}

standalone(); // undefined または window
```

これは意図しない動作の原因になりやすいので、気をつけましょう。

### 3. アロー関数：外側の this をそのまま使う

アロー関数の `this` は、関数が定義された場所の `this` を引き継ぎます。

もう少し正確に言うと、アロー関数は `this` を持たず、外側の `this` をそのまま使います。

```javascript
const user = {
  name: 'Alice',
  // 通常の関数
  greet() {
    const arrow = () => {
      console.log(`こんにちは、${this.name}さん！`);
    };
    arrow();
  },
};

user.greet(); // こんにちは、Aliceさん！
```

アロー関数は `this` を持たず、外側の `this` をそのまま使うので、コールバック関数でよく使われます。

### 4. new による呼び出し：新しいオブジェクトを指す

`new` で関数を呼び出すと、`this` は新しく作られたオブジェクトを指します。

```javascript
function User(name) {
  this.name = name;
}

const alice = new User('Alice');
console.log(alice.name); // Alice
```

これは、JavaScript のクラス構文の内部でも使われています。

### 5. bind/call/apply による呼び出し：this を固定する

`bind`、`call`、`apply` メソッドを使うと、`this` の値を明示的に指定できます。

```javascript
const user = {
  name: 'Alice',
  greet() {
    console.log(`こんにちは、${this.name}さん！`);
  },
};

const greet = user.greet.bind(user);
greet(); // こんにちは、Aliceさん！
```

この方法は、イベントハンドラなどで `this` の値を固定したい場合によく使われます。

## 実践的な使い方：よくあるバグと解決策

### イベントハンドラでの問題

最もよく遭遇する `this` のバグは、イベントハンドラでの問題です。

```javascript
const button = document.querySelector('button');

const user = {
  name: 'Alice',
  handleClick() {
    console.log(`${this.name}がクリックしました！`);
  },
};

// 🙅‍♂️ ダメな例：thisがundefinedになる
button.addEventListener('click', user.handleClick);

// 🙆‍♂️ 良い例1：bindを使う
button.addEventListener('click', user.handleClick.bind(user));

// 🙆‍♂️ 良い例2：アロー関数でラップする
button.addEventListener('click', () => user.handleClick());
```

イベントハンドラ内で `this` を使いたい場合は、`bind` やアロー関数を使って `this` の値を固定しましょう。

### React での this の問題

React でクラスコンポーネントを使う場合も、`this` の問題に遭遇することがあります。

```javascript
class Button extends React.Component {
  // 🙅‍♂️ ダメな例：thisがundefinedになる
  handleClick() {
    this.setState({ clicked: true });
  }

  // 🙆‍♂️ 良い例1：メソッドをアロー関数で定義
  handleClick = () => {
    this.setState({ clicked: true });
  };

  render() {
    // 🙆‍♂️ 良い例2：bindを使う
    return <button onClick={this.handleClick.bind(this)}>Click me!</button>;
  }
}
```

ここでも、`bind` やアロー関数を使って `this` の値を固定することで問題を解決できます。

:::note info
最近の React では関数コンポーネントと Hooks を使うことが推奨されており、その場合は this の問題を気にする必要はありません。
:::

## まとめ

今回説明した内容を整理すると、以下のようになります。

1. `this` は「関数を呼び出したもの」を指す
2. 呼び出し方によって `this` の値が変わる
3. 5 つのパターンを覚えておく
   - メソッド呼び出し
   - 普通の関数呼び出し
   - アロー関数
   - new による呼び出し
   - bind/call/apply による呼び出し

覚えるのが大変そうに見えますが、基本的な考え方さえ理解できれば、あとは実践の中で自然と身についていきます。

最後まで読んでいただき、ありがとうございました！

## 他にもアドベントカレンダー記事を書いています！

他にも、2024 年のアドベントカレンダーに参加しています。

以下の記事でまとめているので、よければ他の記事も読んでいただけると嬉しいです！

https://qiita.com/tomada/items/025b3745df8c484f5856