JavaScript パーフェクト this

<!-- # JavaScript パーフェクト this -->

:::note info
この記事は、2018年04月05日に限定公開として作成したあと、2024年に全体に公開したものです。
:::

JavaScript の this の解説は巷にあふれていますが、私も this をまとめてみたかったのでここにメモ。

## 一般的に語られる4パターン

1. メソッド呼び出しパターン
2. 関数呼び出しパターン
3. コンストラクタ呼び出しパターン
4. call/apply/bind 呼び出しパターン

## その他、覚えておかなければいけないこと

* トップレベルの `this`
* 非Strictモード時の自動ボクシング
* コールバック関数における `this`
* カンマ演算子
* arrow function 呼び出しパターン
* ES class は Strictモード
* Generator の場合

## 1. メソッド呼び出しパターン

JavaScript のメソッドはすべて関数です。オブジェクトのプロパティである関数をメソッドと呼びます。

メソッドを呼び出すときのレシーバが this値 になる。ドットの左側。

```js
'use strict';
const obj = {
  prop: 42,
  func() {
    return this.prop;
  },
};
console.log(obj.func()); //=> 42
```

## 2. 関数呼び出しパターン

レシーバはないので、this値は `undefined` になります。

```js
'use strict';
function func() {
  return this;
}
console.log(func()); //=> undefined
```

関数として定義したか、メソッドとして定義したかということは関係ない。

[**非Strictモード時の注意はここでは述べずに後述します**](#非strictモード時の自動ボクシング)。

## 3. コンストラクタ呼び出しパターン

コンストラクタも関数であり、`new` するとインスタンスを作成して実行します。

this値はそのインスタンスです。

```js
'use strict';
function Foo() {
  this.x = 0;
}
const foo = new Foo();
console.log(foo.x); //=> 0
console.log(foo.hasOwnPropaty('x')); //=> true
```

## 4. call/apply/bind 呼び出しパターン

call/apply/bind で、this値を指定できます。（まさに this値（thisArg）って言葉がよく似合う。）

(1.)メソッド呼び出しパターンと(2.)関数呼び出しパターンのどちらでも指定した値になるし、`undefined` や `null` を指定することもできます。

## トップレベルの `this`

上記の4パターンはすべて、関数の this値についてでしたが、次は トップレベルの `this`。

実行コンテキストが "Script" である場合、トップレベルの `this` はグローバルオブジェクトとなります。

```html
<script>
console.log(this); // => global object
(()=>{
  console.log(this); // => global object
})();
</script>
```

実行コンテキストが "Module" である場合、トップレベルの `this` は `undefined` となります。必ず Strictモードになることに注意。

```html
<script type="module">
console.log(this); // => undefined
(()=>{
  console.log(this); // => undefined
})();
</script>
```

## 非Strictモード時の自動ボクシング

非Strictモードでは、this値がプリミティブならオブジェクトでラップされる。これを「**自動ボクシング (auto-boxing)**」というそうです。

そして、this値が `undefined` または `null` ならグローバルオブジェクトになります。

```js
'use strict';
function func() {
  return this;
}
console.log(func.call('foo')); //=> 'foo'
console.log(func.call(42) === 42); //=> true
console.log(func()); //=> undefined
```

(↑Strictモード ではボクシングされない)

```js
with ({}) {} // not strict mode;
function func() {
  return this;
}
console.log(func.call('foo')); //=> String object
console.log(func.call(42) === 42); //=> false
console.log(func()); //=> global object
```

(↑非Strictモード ではボクシングされる)

これまで紹介したどのパターンであっても「**非Strictモードでは this値が `undefined` または `null` ならグローバルオブジェクトになる**」ことに注意。関数呼び出しパターンはグローバルオブジェクトを返すという説明は半分正解半分間違いです。

## コールバック関数における `this`

コールバック関数をどのように実行するかはその高階関数次第。

ネイティブ関数では this値が `undefined` であることが多いが、 Array#map や Array#filter などでは this値を指定する thisArg 引数が存在します。

## カンマ演算子

カンマ演算子を使えば変数に入れずに(2.)関数呼び出しパターンができます。

```js
'use strict';
const obj = {
  func() {
    return this;
  },
};
(obj.func)() === obj;
(0, obj.func)() === undefined;
```

## arrow function 呼び出しパターン

* [Arrow functions - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)

arrow function では this値が引き継がれます。call/apply/bind の指定は無視されます。

## ES class は Strictモード

* [Classes - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)

class 宣言や class 式 では Strictモードで実行されます。つまり、自動ボクシングは起こりません。

## Generator の場合

* [Generator - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)

Generator関数では Generatorオブジェクトを `next()` しなければ進みませんが、this値はこれまで紹介した各パターンの通り。

ただし、Generatorオブジェクトの `next()` では別のオブジェクトを this値とすることはできません（例外発生）。

```js
'use strict';
const that = { a: 42 };
const genfn = function*(){ while (true) { yield this.a; } };
const g = genfn.call(that);

console.log(g.next().value); //=> 42
//console.log(g.next.call({}).value); //~> TypeError: next method called on incompatible Object
console.log(g.next.call(g).value); // 42
```

## その他注意点

* globalオブジェクトを取得する - Qiita
  * https://qiita.com/Hiraku/items/d249a2f2f13532748324

indirect eval call は常にグローバルスコープで実行されます。

Function コンストラクタは Strictモードが伝播しません。

## 参考文献

* JavaScriptの「this」は「4種類」？？ - Qiita
  * https://qiita.com/takeharu/items/9935ce476a17d6258e27
* Strict モード - JavaScript | MDN
  * https://developer.mozilla.org/ja/docs/Web/JavaScript/Strict_mode
* ECMAScript 2015以降のJavaScriptの`this`を理解する | Web Scratch
  * http://efcl.info/2018/01/04/what-is-this/