【JavaScript】thisを弄ぼう

JavaScriptを勉強していて、誰しも一度はぶつかであろう壁があります。
それが<b>this</b>です。
正直一度で理解できる人は極稀かと思います。
私は2、3度ぶち当たりました！！

今回は、そんな<b>this</b>の真相を紐解いていき弄べるくらいになりましょう。


#thisとは？

<b>this</b>とは呼び出し元のオブジェクトへの参照を保持するキーワードのことです。
<b>this</b>の参照先は、関数の書き方で変わるのではなく、関数の呼び出し方法で変わることに注意してください。
つまり、同じ関数でも呼び出し方法が違えば、<b>this</b>は異なるオブジェクトを参照します。

#関数メソッドとthis
```js
const animal = {
  name: 'dog',
  introduce: function() {
    console.log('The animal is ' + this.name);
  }
}
animal.introduce();  // The animal is dog と出力
```

こちらは``introduceメソッド``の中で、<b>this</b>が使用されています。
先ほども説明した通り、thisは呼び出し元のオブジェクトへの参照を保持しています。
なので今回の`animal.introduce();`内の<b>this</b>は呼び出し元の`animalオブジェクト`を参照しています。

そのため``this.name``とすると``animalオブジェクト``の``nameプロパティ``が取れてくるので、コンソールには``The animal is dog``と表示されることになります。


そして、<b>this</b>は実行コンテキストによって参照先が変わります。

```js
const animal = {
  name: 'dog',
}
console.log('The animal is ' + this.name);
// animalオブジェクトの外に定義すると The animal is と出力されます
```

この場合の<b>this</b>は、``windowオブジェクト``を参照しています。
windowオブジェクトにnameプロパティは存在していないので、``The animal is``のみが出力されることになります。

次のように、windowオブジェクトにnameプロパティが存在していれば、その値を参照してくれます

```js
window.name = 'dog'  // windowオブジェクトにnameプロパティを追加
const animal = {
  name: 'dog',
}
console.log('The animal is ' + this.name);
// The animal is dog と出力されます
```

#参照のコピーとthis

```js
const animal = {
  name: 'dog',
  introduce: function() {
    console.log('The animal is ' + this.name);
  }
}
const animal2 = animal.introduce;
animal2() // The animal is のみ出力されます
```

今回は一度別の変数に代入してから実行しています。
この状態は`変数animal2`に`animalオブジェクト`の`introduceメソッド`がコピーされている状態になります。
すなわち`変数aninmal2`には`introduceメソッドの関数`が代入されている状態です。

```js
// このような状態と同等です
const animal2 = function() {
  console.log('The animal is ' + this.name);
}
```
したがって関数を実行すると、<b>this</b>はグローバルオジェクトを参照し、nameプロパティが存在しなければ`The animal is`のみが出力されます。

```js
window.name = 'dog';
const animal = {
  name: 'dog',
  introduce: function() {
    console.log('The animal is ' + this.name);
  }
}
const animal2 = animal.introduce;
animal2() // The animal is dog と出力されます
```

まとめると、オブジェクトのメソッドとして実行された場合には、thisは呼び出し元のオブジェクトを参照します。
一方で一度変数に代入すると、ただの関数となり実行された場合には、<b>this</b>はグローバルオブジェクトを参照することになります。


もう一度コードを確認してみます。

```js
window.name = 'dog'  // windowオブジェクトにnameプロパティ定義
const animal = {
  name: 'dog',
  introduce: function() {
    console.log('The animal is ' + this.name);
  }
}
animal.introduce();  // The animal is dogと出力
const animal2 = animal.introduce;
animal2();  // The animal is dogと出力される。
```

``animal.introduce();``とした時には、オブジェクトのメソッドとして実行されているため、<b>this</b>は呼び出し元のanimalオブジェクトを参照することになります。
``animal2();``とした時には、関数として実行されるため、windowオブジェクトのnameプロパティを参照することになります。

#コールバック関数とthis

```js
const animal = {
  name: 'dog',
  introduce: function() {
    console.log('The animal is ' + this.name);
  }
}
function fn(callback) {
  callback();
}
fn(animal.introduce); // The animal is のみ出力される
```

今回は`関数fn`の引数として`animal.introduceメソッド`を渡してみました。
そして`関数fn`の中で関数を実行しています。

このとき、出力される値は`The animal is`のみとなっています。
これは`関数fn`に渡されたのが`introduceメソッドの関数`であり、`callback()`では渡ってきた関数を実行しているだけになるので、<b>this</b>はグローバルオブジェクトを参照することになり、`The animal is`のみが出力されます。

これは書き換えるとこのようなコードと同意であると言えます。

```js
const animal = {
  name: 'dog',
  introduce: function() {
    console.log('The animal is ' + this.name);
  }
}
const animal2 = animal.introduce;
animal2()  // The animal is のみ出力される
```

これは先ほどの「参照のコピーとthis」の章で出てきたコードと同じですね。
オブジェクトのメソッドをコールバック関数として呼び出した場合には、関数として実行され、thisはグローバルオブジェクトを参照します。

#bindとthis

bindとは「thisの参照先を指定できて新しい関数を生成する」メソッドです。

ではコードで確認していきます。

```js
const animal = {
  name: 'dog',
  introduce: function() {
    console.log('The animal is ' + this.name);
  }
}
const animal2 = animal.introduce.bind(animal)  // .bind(animal)でthisの参照先を指定
animal2();
```

先ほど説明した通り、`bind`を使って<b>this</b>を束縛すると新しい関数として返却されることになります。

```js
const animal = {
  name: 'dog',
  introduce: function() {
    console.log('The animal is ' + this.name);
  }
}
animal.introduce();   // The animal is dog と出力
const animal2 = animal.introduce.bind(animal)   
animal2();  // The animal is dog と出力
console.log(animal.introduce === animal2)  // 同じように見えるがfalseを返す
```

どちらも`The animal is dog`を結果として出力しますし、見た目は全く同じ関数なのですが、`animal2`は`bind`で作られた新しい関数ですので、`animal.introduce`の関数とは別物であることがコンソールに出力された結果から分かるとおもいます。

#アロー関数とthis

アロー関数とはES6から使用できる、無名関数を省略化して記述する記法のことです。

```js
const animal = {
  name: 'dog',
  introduce: () => { //  「 function() 」 を 「 () => 」 に変更した
    console.log('The animal is ' + this.name);
  }
}
person.hello(); // The animal is のみ出力される
```

今回、`animal`の`introduceメソッド`をアロー関数に書き直してみました。
すると出力結果は、`The animal is`のみとなってしまいました。

これは、アロー関数はthisを持たない性質が関係しています。
つまり、グローバルコンテキストで呼ばれた`animal.introduce`は<b>this</b>を持っておらず、スコープチェーンをたどってwindowオブジェクトを参照することになります。


#まとめ

・thisは、呼び出し元のオブジェクトへの参照を保持する。
・thisは、関数として実行された場合にはwindowオブジェクトを参照する。
・bindやapplyを使うと、thisの参照先を指定できる。
・アロー関数ではthisを持っておらず、thisはスコープチェーンをたどってwindowオブジェクトを参照する。

以上

#宣伝
自分の[Twitter](https://twitter.com/minamino_yuchan)でも有益な情報を発信していますので、興味があればフォローしていただけると幸いです。