【JavaScript】関数とthis

# 概要
JavaScriptを学習、理解を深めるため「[JavaScript Primer 迷わないための入門書](https://jsprimer.net/)」を読み、
理解した内容等を記載していく。

「[【JavaScript】JavaScript入門一覧](https://qiita.com/andota05/items/22fae452225b785fd405)」に他の記事をまとめています。

# この記事で理解できること
* JavaScriptにおけるthisの参照先、参照条件
* 関数・メソッドにおけるthisの挙動
* 関数・メソッドとベースオブジェクトの関係

# 関数とthis
`this`の参照先は、主に以下の条件によって変化するとされている。
* 実行コンテキストにおけるthis
* コンストラクタにおけるthis
* 関数とメソッドにおけるthis
* Arrow Functionにおけるthis(以降、Arrow Function = アロー関数)

# 実行コンテキストとthis
* JavaScriptには実行コンテキストとして、`Script`、`Module`が存在する。
* もっとも外側のスコープにある`this`は、実行コンテキスト(`Script`、`Module`)によって値が異なる。
* 単純にグローバルオブジェクトを参照したい場合は、`this`ではなく`globalThis`を使う(`ES2020`で導入)

## スクリプトにおけるthis
* script要素のtype属性を指定していない場合、ブラウザでは実行コンテキストが`Script`として実行される。
* そのscript要素の直下に書いた`this`はグローバルオブジェクトである`Windowオブジェクト`となる。
* 開発者コンソールで`console.log(this)`と出力してみると`Windowオブジェクト`が出力される。

![スクリーンショット 2022-06-13 6.14.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640274/9edeee5b-0c54-9f18-8c77-27fd6f88d5c8.png)

## モジュールにおけるthis
* script要素に`type="module"`属性がついた場合は、実行コンテキストが`Module`として実行される。
* (HTMLを開発者コンソールで確認してみると)script要素の直下に書いた`this`は`undefined`となる。

```html
<!-- htmlのbody要素などに以下をscript要素をModuleコンテキストとして記載 -->
<script type="module">
  console.log(this);       // => undefined
  console.log(globalThis); // => ブラウザの場合は、Windowsオブジェクトを参照するようになる
</script>
``` 

# 関数とメソッドにおけるthis
* `this`が参照先を決めるルールは、`アロー関数`と`それ以外の関数定義`の方法で異なる。

## 【おさらい】関数・メソッドの定義、呼び出し方法
### 関数の定義方法の種類
関数の定義方法には以下の3つがある
* functionキーワードを使用する
* 関数式
* アロー関数(式)

```js
// functionキーワードを使用する
function func1() {
  // 処理
}

// 関数式
const func2 = function() {
  // 処理
}

// アロー関数(式)
const func3 = () => {
  // 処理
}

// "関数名()"で呼び出し
func1();
func2();
func3();
```

### メソッドの定義方法の種類
* JavaScriptでは`オブジェクトのプロパティ`として定義されている関数を`メソッド`と呼ぶ。

```js
const obj = {
  method1: function() {
    // 処理
  },
  // アロー関数
  method2: () => {
    //処理
  },
  // 短縮記法
  method3() {
    // 処理
  }
}

// "オブジェクト.メソッド名()"でメソッドの呼び出し
obj.method1()
obj.method2()
obj.method3()
```

# アロー関数以外の関数におけるthis
* アロー関数以外の関数における`this`の値は`関数を実行するとき`に決まる。
* `「関数を実行するときに決まる」`背景として、後述の`ベースオブジェクト`が関係している。
* 暗黙的に関数に`this`が渡され、`ベースオブジェクトの状況`によって`this`の内容が変わるイメージ。


```js
// 以降、意図しにくい動作を防止するため"use strict";を明示的に記載する
"use strict";

// 暗黙的にthisは渡ってくる
function func() {
  console.log(this);
}

func(); // => undefined
```

## ベースオブジェクト
* 関数におけるthisの基本的な参照先は`ベースオブジェクト`となる。
* ベースオブジェクトとは、メソッドを呼ぶ際に、そのメソッドの`ドット演算子`または`ブラケット演算子`のひとつ左にある`オブジェクト`のことを言う。
* ベースオブジェクトがない場合の`this`は`undefined`となる。

```js
"use strict";

// ★1 関数(宣言)
function func() {
  console.log(this);
}

// ★2 オブジェクトのメソッド
const obj = {
  method: function() {
    // 処理
    console.log(this);
  }
}

// ★1 ベースオブジェクトはない
func();        // => undefined

// ★2 ドット演算子またはブラケット演算子のひとつ左にある「obj」がベースオブジェクト
obj.method();    // => { method: [Function: method] }
obj["method"](); // => { method: [Function: method] }
```

## 関数宣言や関数式
ベースオブジェクトの例で記載したとおり、関数宣言や関数式には以下のような特徴がある。
* 関数宣言や関数式のベースオブジェクトは存在しない。
* 関数における`this`は`undefined`となるため使い道がない。

```js
"use strict";

// ★1 関数(宣言)
function func() {
  console.log(this);
}

// ★1 ベースオブジェクトはない
func();        // => undefined
```

## メソッド呼び出しにおけるthis
メソッドには以下のような特徴がある。
* JavaScriptでは`オブジェクトのプロパティとして指定される関数`のことを`メソッド`と呼ぶ。
* 結果、メソッドは、`何かしらのオブジェクトに所属している`ことになるため`ベースオブジェクト`は存在する。

```js
"use strict";

// ★2 オブジェクトのメソッド
const obj = {
  method: function() {
    // 処理
    console.log(this);
  }
}

// ★2 ドット演算子またはブラケット演算子のひとつ左にある「obj」がベースオブジェクト
obj.method();    // => { method: [Function: method] }
obj["method"](); // => { method: [Function: method] }
```
結果的にメソッド内で以下のような参照が可能となる。
```js
"use strict";

// オブジェクトのメソッド
const obj = {
  type: "オブジェクト",
  method: function() {
    // 処理
    console.log(this.type); // this.typeは、ベースオブジェクト「obj」のtypeプロパティを指定していることになる
  }
}

obj.method(); // => オブジェクト
```

# thisが問題となるパターン
* 代表的な2つの例として`thisを含むメソッドを変数に代入した場合`と`コールバック関数とthis`がある。

## thisを含むメソッドを変数に代入した場合
```js
"use strict";

const obj = {
  type: "オブジェクト",
  method: function() {
    // 処理
    console.log(this.type);
  }
}

// 変数nonObjMethodにobj.methodを代入(ただの関数となりベースオブジェクトも存在しなくなる)
const nonObjMethod = obj.method;

// ベースオブジェクト、typeというプロパティも存在しないので例外が発生する
nonObjMethod(); // => Cannot read properties of undefine
```

## コールバック関数とthis
* コールバック関数はただの関数であるため`this`は`undefined`となる。<br>(関数宣言や関数式はベースオブジェクトが存在しないため、thisは「`undefinedになる`」と先ほど学びましたね)
* 対処法としては、`this`を一時的に変数に代入する。

```js
"use strict";

const human = {
  suffix: "さん",
  memberCall(members) {
    return members.map(function(member) {
        // (コールバック)関数におけるthisはundefinedなのでsuffixプロパティも存在しない
        return member + this.suffix;
    });
  }
};

const members = human.memberCall(["Taro", "Hanako", "Boby"]); // => Cannot read properties of undefined
console.log(members);
```
下記のように対処することができる。
```js
"use strict";

const human = {
  suffix: "さん",
  memberCall(members) {
    // thisを一時的に変数に代入 -> that(this)はmemberCallにおけるthis -> thisはベースオブジェクトのhuman
    const that = this; 
    return members.map(function(member) {
        // `this`ではなく`that`を参照する
        return member + that.suffix;
    });
  }
};

const members = human.memberCall(["Taro", "Hanako", "Boby"]);
console.log(members); // => [ 'Taroさん', 'Hanakoさん', 'Bobyさん' ]
```

# アロー関数とthis
* アロー関数で定義された関数やメソッドにおける`this`がどの値を参照するかは`関数の定義時（静的）`に決まる。
* アロー関数とそれ以外の関数との大きな違いは、アロー関数は`thisを暗黙的な引数として受けつけない`こと。
* アロー関数には`this`が定義されておらず、常に`外側のスコープを参照する(スコープチェーン)`という特徴がある。

```js
"use strict";

const human = {
  suffix: "さん",
  memberCall(members) {
    return members.map((member) => {
        // アロー関数で記載すると外側のスコープを参照 -> memberCallにはthisがないのでさらに外側のhumanオブジェクトを参照
        return member + this.suffix;
    });
  }
};

const members = human.memberCall(["Taro", "Hanako", "Boby"]);
console.log(members); // => [ 'Taroさん', 'Hanakoさん', 'Bobyさん' ]
```