JavaScriptにおける this を完全に理解する

JavaやKotlin、Swiftなど、多くのオブジェクト指向言語では、this はクラス内メソッドなどで利用され、そのインスタンス自身を指します。
JavaScriptでは挙動が異なります。
本記事では、JavaScriptにおける this の仕様をまとめます。


[りあクト！ TypeScriptで始めるつらくないReact開発 第3.1版【Ⅰ. 言語・環境編】](https://booth.pm/ja/items/2368045)を参考にしています。
めちゃくちゃ良書で、JavaScript・TypeScript、Reactについて深堀りされているのでおすすめの本です！

# this に関するおすすめルール
[りあクト！ TypeScriptで始めるつらくないReact開発 第3.1版【Ⅰ. 言語・環境編】](https://booth.pm/ja/items/2368045)では、下の２つを守ることが推奨されています。

- `this` はClass構文内でしか使わない
- Class構文内では、メソッドを含めたあらゆる関数の定義をアロー関数式で行う

その理由を、this やClass構文の内部的な意味合い、アロー関数式の挙動を踏まえて解説します。

# this は暗黙の関数引数

JavaScriptでは、**this は暗黙的に関数の引数として受け取った、実行時のコンテキストであるオブジェクトそのもの**、と言えます。

「？？？」

という感じだと思うので以下説明します。
例えば、Javaでは以下のようにthisを使うと思います。

```java
class Person {
    String name;

    Person(String name) {
        this.name = name;
    }
    
    void greeting() {
        println("Hi", this.name);
    }
}
```

上記ケースでは、 `greeting` はクラス内メソッドであり、その中で利用されている **this は、 `greeting` 関数の引数として実行インスタンスが暗黙的に渡されているとも解釈できます。**
ですが、わざわざそんな回りくどい説明をする必要はなく、単に実行インスタンス自身を指したものが this である、と説明した方が圧倒的に分かりやすいです。
では、**JavaScriptでは何故単純に実行インスタンスを指していると言わず、実行時コンテキストなのか？**
JavaScriptでは、**this はクラス内メソッド以外の普通の関数でも参照でき、呼び出す状況によって参照先が変わる**からです。
例えば、以下のケース。

```jsx
function myFunc() {
  console.log(this)
}

myFunc() // -> Window {window: Window {...}, self: Window ...}
```

Class内メソッドではなく、剥き出しの関数を呼んでいるだけですが、その内部で this を呼び出せています。
`console.log` の出力は `Window` になっており、これはブラウザ上のJavaScriptでは最上位のオブジェクトを意味しています。（ブラウザ上JavaScriptで扱うすべてのデータはこの `Window` オブジェクトに属する）
つまり、実行側の実行コンテキストがデフォルトでは、最上位オブジェクトでは `Window` になっているわけです。
（Node.js環境だと `Object [global]` になります）

ちなみに、実行コンテキストは意図的に呼び手が改変して明示的に渡すこともできます。

```jsx
function myFunc() {
  console.log(this)
}

myFunc.call({foo: "FOO"}) // -> {foo: "FOO"}
```

`call` 関数は、 `Function` オブジェクトのプロトタイプメソッドで、通常暗黙的に渡される this 引数を明示的に渡すことができます。

# new と this の関係
this を説明する上で、 `new` は深く関係しています。
クラスのインスタンスを作成するものとしてオブジェクト指向プログラミングではお馴染みの`new` ですが、JavaScriptでは少し異なる動きをします。
JavaScriptでは、 `new` を実行すると、**そのプロトタイプオブジェクトを作り、それをコンストラクタの引数に this として暗黙的に与えます。**
ひとつずつ解説します。
まず、**プロトタイプオブジェクトとは、FunctionやArrayオブジェクトが持つプロパティ**です。

オブジェクトごとに設定されているデフォルトオブジェクトみたいなもの。

```jsx
console.log(Array.prototype)            // -> []
console.log(Object.prototype)           // -> {}
console.log((function () {}).prototype) // -> {}
```

そして、 `new` をすることで、対象のプロトタイプオブジェクトを作った上で、そのコンストラクタ関数内での this は作られたプロトタイプオブジェクトを指し示すことになります。
具体的には以下のようなこと。

```jsx
function Animal(name) {
  // this -> Animal Function の prototype (= {}) が暗黙的に引数として与えられる。
  this.name = name;
};
const tiger = new Animal('Tiger');
console.log(tiger); // -> Animal{ name: 'Tiger'}
```

上記のように `Animal` に対して new することで、関数内で this としてプロトタイプオブジェクト（ `{}` ）が与えられています。
つまり上記の例では、与えられた this （ `{}` ）に対して、 `name` というプロパティを追加している、ということになります。
ご存じの通り、 `new` はClassからインスタンスを作るために使わるのが一般的かと思います。
ですが、JavaScriptでは上記の通り、 `new` は関数を呼び出すのに使います。
もちろん、 `new SomeClass()` というように、Classに対して `new` を使うのがJavaScriptでも普通ですよね？
これは、Classが特別なわけではなく、**JavaScriptでの Class の実体はただの関数表現のシンタックスシュガー**、だからです。

```jsx
class AnimalClass {
  constructor(name) {
    this.name = name
  }
}

function AnimalFunc(name) {
  this.name = name
}

// 同じ
const lionClass = new AnimalClass('Lion')
const lionFunc  = new AnimalFunc('Lion')

console.log(lionClass.name) // -> Lion
console.log(lionFunc.name)  // -> Lion
```

上記は、Classを `class` を使って表現した `AnimalClass` と、 `function` を使って表現した `AnimalFunc` を比べたものです。
見た目は違いますが、挙動は同じように動いています。

# `.` で連結されたメソッド内で実行された時の this

```jsx
const someObj = {
  name: "Some Object",
  run: function() {
    console.log(this)
  }
}

someObj.run() // -> {name: "Some Object", run: ƒ run()}
```

上記のケースでは、 `run` に暗黙的に渡されている引数 this は、 `.` で連結されている `someObj` 自体を指しています。
これは、Javaなどの他のオブジェクト指向言語でのインスタンス参照としての this と似ていて理解しやすいですね。

# strict mode と this の関係

冒頭に書いた通り、単に関数の中で this を参照するなど、と、グローバル変数として機能して、最上位オブジェクトである `Window` (ブラウザ環境) or `Object [global]` (Node.js環境)が参照されます。
これを利用すると、簡単にグローバル変数の汚染ができてしまいます。

```jsx
function myFunc() {
  this.AAA = "Dummy AAA..."
  console.log(this)
}

myFunc()
// (出力)
// <ref *1> Object [global] {
//  (略)
//  AAA: 'Dummy AAA...'   <--- グローバル変数に好きなプロパティを追加できてしまった！！
// }
```

これを防ぐために、 **strict mode** というものがES2015から実装されています。

```jsx
function myFunc() {
  'use strict' // <--- strict mode を指定

  this.AAA = "Dummy AAA..."
  console.log(this)
}

myFunc() // -> TypeError: Cannot set properties of undefined (setting 'AAA')
```

これを利用すると、上記の通り this が undefined になり、グローバル変数としての this にアクセスすることを防いでくれます。
正確には、**実行コンテキストが存在しない場合、 this が undefined になります。**
これはグローバル変数としての this へのアクセスを防ぐだけなので、 `new` での暗黙的 this の注入は参照できます。

```jsx
new myFunc() // -> myFunc { AAA: 'Dummy AAA...' }
```

Class は単なる関数のシンタックスシュガーですが、**Class には自動的に strict mode になり、更に new しないと呼び出せない**、という特徴があります。
よって、**Class の方はいちいち strict mode にする必要がない点で安全で便利**と言えます。

# クラスメソッド内関数での this に起きる不都合

以下のようなClassを使ったコードを実行するとエラーになります。

```jsx
class MyClass {
	constructor(param) {
    this.param = param;
  }

  run() {
    function _run() {
      console.log(`this.param: ${this.param}`)
    }
    _run()
  }
}

const myClass = new MyClass("HELLO")
myClass.run()
// -> TypeError: Cannot read properties of undefined (reading 'param')
```

エラーの理由は、

1. `run()` *実行時にコンテキストが何もないので、* `_run()` には暗黙的に最上位オブジェクトが this として入ろうとする。
2. Class内では自動的に `strict mode` になっている。
3. `_run` 関数内で this にアクセスは出来なくなりエラー。

上記のコードは、Javaなどの一般的なオブジェクト指向言語からすると、 `_run()` 内の this もクラスのインスタンス（上記でいうと `myClass` ）を参照することを期待しそうで、感覚的にエラーが理解しにくいと感じます。

これを回避するにはいくつかの方法がありますが、最も他のオブジェクト指向言語の感覚と似た形で解決する方法は、**アロー関数を使うようにする**ことです。

# アロー関数での this の扱いの特徴

アロー関数には、通常の関数と違い、以下のような特徴があります。

- 暗黙的引数としての this を持たない
- this アクセスするときは関数の外のスコープの this を参照する

先ほどの例をアロー関数で書き直します。

```jsx
class MyClass {
	constructor(param) {
    this.param = param;
  }

  run() {
    const _run = () => {
      console.log(`this.param: ${this.param}`)
    }
    _run()
  }
}

const myClass = new MyClass("HELLO")
myClass.run()
// -> this.param: HELLO
```

上記のように、**アロー関数によって一般的なオブジェクト指向言語の感覚と一致した理解しやすい動き**になりました。

細かく説明すると、 `_run()` はアロー関数なので、暗黙的引数の this を持ちません。その代わりにその外側である、 `run()` のスコープの this を参照します。

`run()` の this は、「`.` で連結されたメソッド内で実行された時の this」の扱いとなり、 `myClass.run()` という呼び方からも、 this と `myClass` は等しくなってくれます。

# まとめ

以上を踏まえて、[りあクト！ TypeScriptで始めるつらくないReact開発 第3.1版【Ⅰ. 言語・環境編】](https://booth.pm/ja/items/2368045)では、最初に挙げた下の２つを守ることが推奨されているわけです。
🙇‍♂️
- `this` はClass構文内でしか使わない
- Class構文内では、メソッドを含めたあらゆる関数の定義をアロー関数式で行う

これまで、上記のようなルールを聞いたことがある人は多いかもしれませんが、ここまでの流れを読めばその理由を本当に理解することができるかと思います。