【JS初学者へ送る】thisとbind()を徹底解説(アロー関数との違いも)

# JavaScriptの`bind()`メソッド解説

## 1. はじめに
下記のコードでなぜ`this.name`の出力結果がundefinedになるか、あなたは説明できるでしょうか？
```js
const person = {
    name: "Alice",
    greet: function () {
        console.log("こんにちは、" + this.name + "です。");
    }
};
const greetFunction = person.greet;
greetFunction(); // 出力: こんにちは、undefinedです。
```
説明できなかった方この記事を読んで損はないはずです:writing_hand:

JavaScriptの`bind()`メソッドは、関数の挙動を制御するためのメソッドですが、初学者が非同期処理と同じくらい理解不能に陥ってしまうのがこの`bind()`です。

今回は`bind()`の使い方と概要について解説していきます。

## 2. 関数の呼び出し方とthisの値

まず。大前提として理解しておかないといけないことは、**JavaScriptでは関数の呼び出し方によって`this`の値が変わる** というころです。

まずの次の見慣れた形を見ていきましょう。
### 2.1 オブジェクトのメソッドとして呼び出す

```javascript
const person = {
  name: "Alice",
  greet: function() {
    console.log("こんにちは、" + this.name + "です。");
  }
};

person.greet(); // 出力: こんにちは、Aliceです。
```

この場合、`this`は`person`オブジェクトを指します。よく見る通常のthisですね。

### 2.2 単独の関数として呼び出す場合
では冒頭に出てきたコードをもう一度確認してみましょう。
以下のように呼び出すとどうなるでしょうか？？

```javascript
const person = {
    name: "Alice",
    greet: function () {
        console.log("こんにちは、" + this.name + "です。");
    }
};
const greetFunction = person.greet;
greetFunction(); // 出力: こんにちは、undefinedです。

```

ご覧の通り出力結果が`undefind`となってしまいます。
これは、関数(greet)が**オブジェクトから切り離されて呼び出されているため**thisはglobalオブジェクトを探しに行った結果、見当たらずundefindとなっています。

### 初心者用にさらに詳しく解説（理解できている人はスキップ可）:writing_hand:

1. 関数の切り離し
   ```javascript
   const greetFunction = person.greet;
   ```
   この行で、`person.greet`関数を`greetFunction`変数に代入しています。この時点で、関数は`person`オブジェクトから「切り離された」状態になります。
   <br>

2. 切り離された関数の呼び出し:
   ```javascript
   greetFunction("Hello");
   ```
   この呼び出しでは、関数は`person`オブジェクトのメソッドとしてではなく、独立した関数として実行されます。
   <br>

3. `this`の挙動
   - JavaScriptでは、関数が単独で呼び出された場合（つまり、オブジェクトのメソッドとしてではなく）、`this`は**デフォルトでグローバルオブジェクトを参照します。**
   - ただし、strict modeではこのデフォルトの動作が変わり、`this`は`undefined`になります。

4. `undefined`の結果:
   - 関数内で`this.name`にアクセスしようとしても、`this`がグローバルオブジェクト（またはstrict modeでは`undefined`）を指しているため、`name`プロパティは存在しません。
   - そのため、`this.name`は`undefined`となり、結果として「Hello, I'm undefined」のような出力になります。

### そして、これを解決してくれるのが**bind()** です。
次の章で解説しますが先に簡単に予習しておきましょう。
例えば以下のように、切り離された関数に対してbind()を使用すると...

```javascript
const boundGreet = person.greet.bind(person);
boundGreet("Hello"); // 正しく "Hello, I'm Alice" と出力されます
```

このように、`bind()`によってグローバルオブジェクトを探しに行く`this`に対して「おいおい、そこまで探しに行かなくていいよ！この値をみればいいよ！」という風に`person`オブジェクトに固定(bind)してくれるというわけです。


## 3. bind()メソッドの役割

`bind()`メソッドは、関数の`this`値を固定する新しい関数を作成します。

### 3.1 基本的な使い方

```javascript
const boundGreet = person.greet.bind(person);
boundGreet(); // 出力: こんにちは、Aliceです。
```

`bind(person)`は、グローバルオブジェクトを探しに行く`this`を`person`オブジェクトに固定した新しい関数を作ります。

### 3.2 引数の事前設定

`bind()`は`this`の固定だけでなく、引数も予め設定できます。

```javascript
function greet(greeting, name) {
  console.log(greeting + ", " + name + "!");
}

const greetAlice = greet.bind(null, "こんにちは");
greetAlice("Alice"); // 出力: こんにちは, Alice!
```

## 4. bind()が役立つ場面

1. コールバック関数で元のオブジェクトの参照を維持したい場合
2. 一部の引数を固定した新しい関数を作成する場合
3. イベントリスナーで特定のオブジェクトのメソッドを使用する場合

各項目を実際のコード例で見ていきましょう

### 4.1 コールバック関数で元のオブジェクトの参照を維持したい場合

```javascript
const user = {
  name: "Alice",
  greet: function() {
    console.log("こんにちは、" + this.name + "です。");
  }
};

// bindを使用しない場合
setTimeout(user.greet, 1000);
// 1秒後に出力: こんにちは、undefinedです。

// bindを使用する場合
setTimeout(user.greet.bind(user), 1000);
// 1秒後に出力: こんにちは、Aliceです。
```

この例では、`setTimeout`のコールバックとして`user.greet`を渡す際に`bind()`を使用することで、`this`が`user`オブジェクトを正しく参照するようになります。

### 4.2 一部の引数を固定した新しい関数を作成する場合

```javascript
function multiply(a, b) {
  return a * b;
}

// 5を固定した新しい関数を作成
const multiplyByFive = multiply.bind(null, 5);

console.log(multiplyByFive(3)); // 出力: 15
console.log(multiplyByFive(7)); // 出力: 35
```

この例では、`bind()`を使って最初の引数を5に固定した新しい関数を作成しています。`null`は`this`の値を変更しないことを示しています。

### 4.3 イベントリスナーで特定のオブジェクトのメソッドを使用する場合
```html
<button id="incrementButton">増加</button>
```

```javascript
const calculator = {
  value: 0,
  increment: function() {
    this.value++;
    console.log("現在の値: " + this.value);
  }
};

const button = document.getElementById("incrementButton");

// bindを使用しない場合
button.addEventListener("click", calculator.increment);
// クリック時に "現在の値: NaN" と出力される

// bindを使用する場合
button.addEventListener("click", calculator.increment.bind(calculator));
// クリック時に正しく "現在の値: 1", "現在の値: 2", ... と出力される
```

この例では、イベントリスナーにメソッドを渡す際に`bind()`を使用することで、`this`が`calculator`オブジェクトを正しく参照するようになります。

# アロー関数と通常の関数における`this`の挙動と`bind()`の使用
まずbind()の説明に入る前にthisの挙動について理解しておかないといけません。
初心者がないがしろにしがちなのですが、**アロー関数は通常の関数とthisの扱いが異なります。**

結論から言うと、**通常の関数ではthisの値は呼び出し時に決定されますが、アロー関数では関数が定義された場所（レキシカルスコープ）のthisを引き継ぎます。**

詳しく見ていきましょう。

## 1. 通常の関数の`this`

通常の関数では`this`の値は**呼び出し時**に決定されます。

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

const obj1 = { name: 'Alice', method: regularFunction };
const obj2 = { name: 'Bob' };

obj1.method();           // 出力: 'Alice'
regularFunction();       // 出力: undefined (strictモードでは error)
regularFunction.call(obj2); // 出力: 'Bob'
```

通常の関数は、呼び出し方によって`this`の値が変わります。

## 2. アロー関数の`this`

アロー関数の`this`は、関数が**定義された時点**で決まり、**後から変更できません。**

```javascript
const arrowFunction = () => {
  console.log(this.name);
};

const obj1 = { name: 'Alice', method: arrowFunction };
const obj2 = { name: 'Bob' };

obj1.method();           // 出力: undefined
arrowFunction();         // 出力: undefined
arrowFunction.call(obj2); // 出力: undefined
```

アロー関数の`this`は、定義されたスコープの`this`を引き継ぎます。
つまり、上記の例ではグローバルスコープに定義されたアロー関数内のthisはグローバルオブジェクトを探しに行くので参照先(name)が見当たらずundefindとなっています。

#### ではどうやればうまくthisがnameを参照してくれるのでしょうか？？
```js
const obj = {
  name: 'Alice',
  arrowMethod: null,
  init: function() {
    this.arrowMethod = () => {
      console.log(this.name);
    };
  }
};

obj.init();
obj.arrowMethod(); // 出力: 'Alice'
```
この例ではアロー関数がobjのメソッド内で定義されているため、objのthisがnameを参照してくれています。


## 3. `bind()`の使用

### 3.1 通常の関数での`bind()`

通常の関数では、`bind()`を使って`this`を固定できます。

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

const obj = { name: 'Charlie' };
const boundRegular = regularFunction.bind(obj);

boundRegular(); // 出力: 'Charlie'
```

### 3.2 アロー関数での`bind()`（誤った使用）

アロー関数に`bind()`を使用しても、`this`は変更されません。

```javascript
const arrowFunction = () => {
  console.log(this.name);
};

const obj = { name: 'Charlie' };
const boundArrow = arrowFunction.bind(obj);

boundArrow(); // 出力: undefined
```
つまり、**アロー関数ではbind()を使用すべきではない**ということです。
それは先に説明した通り、thisの挙動が異なりbind()を使用したところで効果を発揮しないからです。
（探したら無理やり使う方法あるかも...）

### 3.3 アロー関数の正しい使用例

アロー関数は、外部スコープの`this`を利用したい場合に有用です：

```javascript
const obj = {
  name: 'Alice',
  delayedGreeting: function() {
    setTimeout(() => {
      console.log('Hello, ' + this.name);
    }, 1000);
  }
};

obj.delayedGreeting(); // 1秒後に出力: 'Hello, Alice'
```

この例では、`setTimeout`のコールバックとしてアロー関数を使用することで、`obj`の`this`を正しく参照してくれています。

#### 通常の関数とアロー関数とで比較する
```javascript
const obj = {
  name: 'Charlie',
  arrowFunc: () => {
    console.log(this.name);
  },
  regularFunc: function() {
    console.log(this.name);
  }
};

obj.arrowFunc();    // 出力: undefined （thisはグローバルスコープ）
obj.regularFunc();  // 出力: 'Charlie'

const boundArrow = obj.arrowFunc.bind(obj);
boundArrow();       // 出力: undefined （bindは効果なし）

const boundRegular = obj.regularFunc.bind({ name: 'Dave' });
boundRegular();     // 出力: 'Dave'
```

## 4. `bind()`が必要な場合（通常の関数）

繰り返しになりますが`bind()`が必要な場合は、通常の関数を使用するという考え方を持っておくのがベターです。

```javascript
const user = {
  name: 'Bob',
  greet: function() {
    console.log('Hello, ' + this.name);
  }
};

const greet = user.greet;
greet(); // 出力: 'Hello, undefined'

const boundGreet = user.greet.bind(user);
boundGreet(); // 出力: 'Hello, Bob'
```

# thisに関連する `call()`と`apply()`
  **※初学者はスキップ可能**
`call()`と`apply()`メソッドは、bind()と同様に関数の呼び出し方を制御し、`this`の値を明示的に設定するために使用されます。

##  `call()`メソッド

1. 関数内で使用される `this` の値を明示的に設定する
2. 関数に引数を渡す

### 1. 基本的な構文

```javascript
function.call(thisArg, arg1, arg2, ...)
```

- `thisArg`: 関数内で `this` として使用される値
- `arg1, arg2, ...`: 関数に渡す引数（複数可）

### 2. シンプルな例

まず、`call()`を使わない通常の関数呼び出しを見てみましょう：

```javascript
function greet() {
  console.log('こんにちは、' + this.name + 'さん');
}

const person = { name: '太郎' };

// 通常の呼び出し
greet(); // 出力: "こんにちは、undefinedさん"
```

ここで、`call()`を使用して `this` を設定します：

```javascript
greet.call(person); // 出力: "こんにちは、太郎さん"
```

### 3. 引数を渡す例

`call()`は `this` の設定だけでなく、関数に引数を渡すこともできます：

```javascript
function introduce(hobby, age) {
  console.log('私は' + this.name + 'です。' + hobby + 'が好きで、' + age + '歳です。');
}

const person = { name: '花子' };

introduce.call(person, '読書', 25);
// 出力: "私は花子です。読書が好きで、25歳です。"
```

### 4. `call()`の実用的な使用例

#### 4.1 配列のようなオブジェクトを本物の配列に変換

```javascript
function convertToArray() {
  return Array.prototype.slice.call(arguments);
}

const args = convertToArray(1, 2, 3);
console.log(args); // 出力: [1, 2, 3]
```

ここでは、`Array.prototype.slice`メソッドを`arguments`オブジェクト上で呼び出しています。

※上記方法はES6（ECMAScript 2015）以前によく使用されていましたが、現在では以下のような代替方法があります
```javascript
function convertToArray(...args) {
  return args;
}

function convertToArray() {
  return Array.from(arguments);
}
```

#### 4.2 メソッドの借用

```javascript
const numbers = { 0: 10, 1: 20, 2: 30, length: 3 };

const sum = Array.prototype.reduce.call(numbers, (acc, curr) => acc + curr, 0);
console.log(sum); // 出力: 60
```

この例では、配列ではないオブジェクトに対して配列のメソッド（`reduce`）を使用しています。こちらも同様にモダンJSでは以下のように記載します
```js
const numbers = { 0: 10, 1: 20, 2: 30, length: 3 };

// Array.fromを使用する
const sum = Array.from(numbers).reduce((acc, curr) => acc + curr, 0);

// スプレッド構文を使用する
const sum = [...numbers].reduce((acc, curr) => acc + curr, 0);
```

### 5. `call()`と通常の関数呼び出しの違い

1. `this`の明示的な設定:
   `call()`を使用すると、関数内の`this`の値を明示的に設定できます。

2. 関数の即時実行:
   `call()`は関数を即座に実行します。

3. 引数の渡し方:
   `call()`では引数を個別に列挙して渡します。

### 6. call()まとめ

- `call()`メソッドは関数の`this`値を制御し、同時に引数を渡すための強力なツールです。
- 主に以下の場面で使用されます：
  - オブジェクトのメソッドを他のオブジェクトで使用する
  - 配列のようなオブジェクトに配列メソッドを適用する
  - 関数の借用
- `call()`を理解し適切に使用することで、より柔軟で再利用可能なコードを書くことができます。

## 3. `apply()` メソッド

### 3.1 基本的な使い方

`apply()`メソッドは`call()`と似ていますが、**引数を配列として渡します。**

構文：`function.apply(thisArg, [argsArray])`

```javascript
function greet(greeting) {
  console.log(greeting + ', ' + this.name);
}

const person = { name: 'Charlie' };

greet.apply(person, ['Hello']); // 出力: "Hello, Charlie"
```
上記をcallを使用すると下記のようになります
```js
greet.call(person, 'Hello');
```

### 3.2 複数の引数を使用する例

```javascript
function introduce(greeting, profession) {
  console.log(greeting + ', I\'m ' + this.name + ' and I\'m a ' + profession);
}

const person = { name: 'David' };

introduce.apply(person, ['Hi', 'teacher']);
// 出力: "Hi, I'm David and I'm a teacher"
```

## 4. `call()` と `apply()` の違い

主な違いは引数の渡し方です：
- `call()`: 引数を個別に渡します。
- `apply()`: 引数を配列として渡します。

```javascript
function sum(a, b, c) {
  return a + b + c;
}

console.log(sum.call(null, 1, 2, 3));    // 出力: 6
console.log(sum.apply(null, [1, 2, 3])); // 出力: 6
```

## 5. 実践的な使用例

### 5.1 配列の操作

```javascript
const numbers = [5, 6, 2, 3, 7];

// Math.maxは通常、数値のみを引数に取りますが、
// applyを使用することで配列の要素を引数として渡せます
const max = Math.max.apply(null, numbers);

console.log(max); // 出力: 7
```

### 5.2 メソッドの借用

```javascript
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };

// Array.prototypeのsliceメソッドを借用して配列に変換
const array = Array.prototype.slice.call(arrayLike);

console.log(array); // 出力: ['a', 'b', 'c']
```

こちらもモダンJSでは以下のように記載
```js
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const array = Array.from(arrayLike);
console.log(array); // 出力: ['a', 'b', 'c']
```

## 6. 注意点

- アロー関数では、`call()`や`apply()`を使用しても`this`の値は変更されません。
- `null`や`undefined`を`thisArg`として渡した場合、グローバルオブジェクト（ブラウザでは`window`）が`this`として使用されます（strictモードを除く）。

## 5. まとめ

1. 通常の関数：
   - `this`は呼び出し時に決定される
   - `bind()`, `call()`, `apply()`で`this`を変更できる
   - オブジェクトメソッドやイベントリスナーに適している

2. アロー関数：
   - `this`は定義時に決定され、変更できない
   - `bind()`, `call()`, `apply()`は`this`に影響しない
   - コールバックやレキシカルな`this`が必要な場面に適している

適切な関数の種類を選ぶことで、`this`の挙動に関する多くの問題を回避できます。状況に応じて、通常の関数とアロー関数を使い分けることが重要です。