JavaScriptのthisについて

# 初めに
今回は`this`について自分なりにまとめたものをアウトプットしたいと思います。
主にこの二つの文章を参考したうえの勉強メモです。

https://www.codementor.io/@dariogarciamoya/understanding--this--in-javascript-du1084lyn

https://www.codementor.io/@dariogarciamoya/understanding-this-in-javascript-with-arrow-functions-gcpjwfyuc

検証を書いたら文章が長くなってしまって、TLDRの方は[結論](https://qiita.com/hu-yu/items/74529effd585de1d70ca#%E7%B5%90%E8%AB%96)だけ読んでもいいと思います。

# `this`とは
まずはMDNの説明によると、
>ほとんどの場合、this の値はどのように関数が呼ばれたかによって決定されます (実行時結合)。これは実行時に代入によって設定することはできず、関数が呼び出されるたびに異なる可能性があります。
[this - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this)

strict モードではない`this`はどこに存在するのに関係なく、どのように呼び出されることに関連している。しかし`call()`、`apply()`、`bind()`バインドメソッドの導入に連れ、`this`の応用範囲が一気に拡大しました。
>ES5 では bind() メソッドが導入され、関数がどのように呼ばれたかに関係なく `this` の値を設定するすることができるようになり、ES2015 では、自身では this の結び付けを行わないアロー関数が導入されました (これは包含する構文上のコンテキストの this の値を保持します)。
[this - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this)

ES5で導入されたメソッドによって値を設定することが可能になった。（説明文はbind()を書いてあるが、実際はほかにcall()、apply()も`this`のコンテキストを決定することができる）
実際調べたら、`this`は扱い方によっていくつの形態があるそうです。まずは`this`の概念からまとめていきたいと思います。

# Default `this` context & Object literals`{this}`
`this`の概念は、関数が生成されたとき、その背後に`this`というキーワードも創られ、その関数の執行範囲（コンテキスト）を決定する。

そして範囲を決定するというのは、どう呼び出されるのかにつながるので、
```jsx
// default this context
const myFunction = function () {
  console.log(this)
}
myFunction()
// Object [global]
// this refers to window
```
`myFunction()`のように**global scope**で呼び出され、ここの`this`は`window`というオブジェクトにコンテキストを決めるようになる。
```jsx
// object literals
const a = 1
const myMethod = function () {
  const a = 2
  console.log(this.a)
  console.log(this)
}

const myObject = {
  a: 3,
  myMethod: myMethod
}
myMethod()
// undefined
// Object [global] === window
// this refers to window
// note: there dose not exist a variable called 'a' in window object

myObject.myMethod()
// 3
// { a: 3, myMethod: [Function: myMethod] } === myObject
// this refers to myObject
```
`myMethod()`は`myObject`というブロックスコープ（Block Scope）から呼び出され、`myObject`オブジェクトにコンテキストを決める。

そして、もし多重スコープ内で呼び出されたら、
```jsx
// multiple scopes
const outerObject = {
  name: 'Mick',
  age: 20,
  innerObject: {
    name: 'Lucy',
    age: 24,
    fn: function () {
      console.log(this.name, this.age)
      console.log(this)
    }
  }
}

outerObject.innerObject.fn()
// Lucy 24
// { name: 'Lucy', age: 24, fn: [Function: fn] } === innerObject
// this refers to innerObject
```
どんな多重スコープであっても、`fn`のように`this`は一個上のコンテキストに直結している。

これでMDNの説明文、関数がどこで宣言されたかに関係なく、どう呼び出されたに関連しているということがわかるようになりました。
そして`this`のこのような動きが、 **Implicit binding（暗黙の結合）** とも呼ばれています。

ES5から`call()`、`apply()`、`bind()`で強制的に`this`のコンテキストを変えることもできるようになって、**Explicit binding（明確な結合）** と **Hard binding（強力な結合）** というやり方も出てきました。
（日本語と英語が母語ではありませんので、変な和訳してしまったらご容赦ください）

# Explicit binding
`call()`と`apply()`は **Explicit binding（明確な結合）** に属して、
```jsx
const myMethod = function (a, b, c) {
  console.log(this)
  console.log(a * b * c)
}

const myObject = {
  myMethod: myMethod
}

myMethod() // Object [global] === window // NaN
myMethod.call(myObject, 1, 2, 3) // myObject // 6
myMethod.apply(myObject, [4, 5, 6]) // myObject // 120
myMethod.call(null, 1, 2, 3) // Object [global] === window // 6
myMethod.apply(null, [4, 5, 6]) // Object [global] === window // 120
/* note:
func.call([thisArg[, arg1, arg2, ...argN]])
func.apply(thisArg, [argsArray])
*/
```
上例のように`call()`と`apply()`は強制的に`myMethod`のコンテキストを`myObject`に変えるようになった。また`call()`と`apply()` メソッドの違いは後ろのパラメータ `arg` の引数の形態です。

そしてDOM操作のとき返してきた配列風オブジェクトの処理ではこれらのメソッドよく使われています。（ここは単純にオブジェクトを例として書いてみたが実際はもっと複雑なものになる）
```jsx
// another example
const DOMObject = {
  length: 2,
  0: 'first',
  1: 'second'
}

console.log(Array.prototype.slice.call(DOMObject, 0, 2)) // [ 'first', 'second' ]
console.log(Array.prototype.slice.apply(DOMObject, [0, 2])) // [ 'first', 'second' ]
console.log([].slice.call(DOMObject, 0, 2)) // [ 'first', 'second' ]
// note: [].slice.call(document.querySelectorAll('...'), arg1, arg2)
console.log(Array.from(DOMObject)) // [ 'first', 'second' ]
```
# Hard binding
`bind()`は **Hard binding（強力な結合）** として、一番強制力のあるメソッドです。
`call()`と`apply()`と違って、`bind()`で一旦`this`コンテキスト決めたら`call()`や`apply()`使っても変えることができません。


```jsx
let myMethod = function () {
  console.log(this.a)
  console.log(this)
}

const obj1 = {
  a: 2
}

const obj2 = {
  a: 3
}

myMethod = myMethod.bind(obj1)
console.log(myMethod())
// 2
// { a: 2 } === obj1
// this refers to obj1

myMethod.call(obj2)
// 2
// { a: 2 } === obj1
// this still refers to obj1

// note: bind() -> function currying
```
また、`call()`と`apply()`はほかの関数と連携して値を返してもらうことに対して、`bind()`はコンテキストだけを決めていろいろ応用するのが多いそうです。
```jsx
// freeze btn 3s
const btn = document.querySelector('button')
btn.addEventListener('click', eventHandler)

function eventHandler() {
  this.disabled = true
  console.log(this) // btn

  setTimeout(function () {
    this.disabled = false
    console.log(this) // window
    // this refers to window
    // btn won't retrieve
  }, 3000)
  console.log(this)  // btn
}
// use bind()
function eventHandler() {
  this.disabled = true
  console.log(this) // btn

  setTimeout(function () {
    this.disabled = false
    console.log(this) // btn
    // this refers to btn
  }.bind(this), 3000)
  console.log(this) // btn
}

// don't know the difference
// btn.addEventListener('click', function eventHandler() {
//   this.disabled = true
//   console.log(this) // btn

//   setTimeout(function () {
//     this.disabled = false
//     console.log(this) // window
//     // this refers to window
//     // but btn will be functional
//   }, 3000)
//   console.log(this)  // btn
// })
```
`setTimeout()`は**WebAPIs**なので、`bind()`使わない限り`this`はブラウザ`window`に参照している。
（別の話ですけれど、`addEventListener`の内（inside）に書いてみると、`this`は`window`に参照しているが、なぜか`btn`は正しく動作してくれました。原因はまだわからないですが...）

そして、`bind()`の使い方を調べながら試行錯誤のなか、何となく分割代入（Destructuring assignment）と概念が似ている。（ちなみに`bind()`はオブジェクトとの結合により特定の引数を設定し、関数の土台になるというやり方もあります。）
MDNの例をご参考にしてください↓

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#partially_applied_functions

```jsx
function foobar(something) {
  this.a = something
  console.log(this)
}

const obj = {}
const fb = foobar.bind(obj)
fb(2) // { a: 2 }
console.log(obj.a) // 2

// note: it's similar to
// const obj = {}
// const { a } = obj
// obj.a = 2
// console.log(obj.a) // 2
```

## Weight of the binding
**Implicit binding < Explicit binding < Hard binding < `new` binding**

# New binding
```jsx
// new instance
function foo(something) {
  this.name = 'foo'
  this.a = something
  console.log(this.name)
  console.log(this.a)
  console.log(this)
}

foo(2)
// foo
// 2
// Object [global] {..., name: 'foo', a: 2} === window
// this refers to window

const f = new foo(5)
// foo
// 5
// foo { name: 'foo', a: 5 }
// this refers to foo itself

// console.log(f.name) // foo
// console.log(f.a) // 5
```
`new`は新しいインスタンスを作っていくため、そのなかの`this`は常に新しいコンテキストに参照する。
`bind()`使ってもインスタンス中の`this`を変えることができません。
# `this` in `bind()` & `new`
```jsx
// this in bind() & new
function foo(something) {
  this.a = something
}

const obj = {}
const bar = foo.bind(obj)
bar(2)
console.log(obj.a) // 2

const newBar = new bar(3)
console.log(obj.a) // 2
console.log(newBar.a) // 3
```

# `this` in Arrow function
```jsx
// arrow function as methods
const myArrowMethod = () => {
  console.log(this.name)
  console.log(this)
}

const arrow = {
  name: 'Luna',
  myArrowMethod: myArrowMethod
}
myArrowMethod()
// undefined
// {} === window or Object [global]

arrow.myArrowMethod()
// undefined
// {} === window or Object [global]
```
>arrow functions don't bind their own scope, but inherit it from the parent one, which in this case is window or the global object.
[Arrow functions as methods](https://www.codementor.io/@dariogarciamoya/understanding-this-in-javascript-with-arrow-functions-gcpjwfyuc#arrow-functions-as-methods)

アロー関数のスコープ（コンテキスト）は親から継承するので、コンテキストも`window`か`object [global]`に参照している。（`arrow.myArrowMethod`の親である`myArrowMethod`のコンテキストは`window`に参照しているから）

そして下のように書き方を変えたら、親である`myArrowMethod`はすでにオブジェクト`arrow`に入っているので、スコープを継承した`myArrowFunction`も同じコンテキストに指しています。
```jsx
const arrow = {
  myArrowFunction: null,
  myArrowMethod: function () {
    this.myArrowFunction = () => {
      console.log(this)
    }
  }
}

// call `myArrowMethod` to initialize `myArrowFunction`
arrow.myArrowMethod()
// call `myArrowFunction` to print `this`
arrow.myArrowFunction()
// {
//   myArrowFunction: [Function(anonymous)],
//   myArrowMethod: [Function: myArrowMethod]
// }

const myArrowFunction = arrow.myArrowFunction
myArrowFunction()
// {
//   myArrowFunction: [Function(anonymous)],
//   myArrowMethod: [Function: myArrowMethod]
// }
```
そしてアロー関数に対し、`call()`、`apply()`、`bind()`メソッドでコンテキストを変えることができません。
```jsx
const myArrowFunction = () => {
  console.log(this)
}

const myObject = {}

myArrowFunction.call(myObject) // {} === window or Object [global]
myArrowFunction.apply(myObject) // {} === window or Object [global]

const myFunctionBound = myArrowFunction.bind(myObject)

myFunctionBound() // {} === window or Object [global]

const newFunction = new myArrowFunction()
// TypeError: myArrowFunction is not a constructor

/* note: arrow functions can't be called as constructors, 
and the prototype property doesn't exist for arrow functions
*/
```
`new`を使って新しいイスタンスを作ることも不可能です。
詳細についてこちらご参考してください↓

https://blog.logrocket.com/anomalies-in-javascript-arrow-functions/#:~:text=Arrow%20functions%20cannot%20be%20used,result%2C%20bindings%20such%20as%20new

# API asynchronous callbacks
例えば`setTimeout()`や`addEventListener()`などのasynchronous codeはブラウザが提供した関数なので、常に`window`に指しているが、`bind()`かアロー関数でコンテキストを変えることが可能です。
```jsx
// those examples all make btn functional
// example1
btn.addEventListener('click', () => {
  this.disabled = true
  console.log(this) // window

  setTimeout(function () {
    this.disabled = false
    console.log(this) // window
  }, 3000)
  console.log(this) // window
})
// note: it's functional but I don't know why it works
```
`example1`のように`setTimeout()`と`addEventListener()`のコンテキスト`window`に指している。

```jsx
// example2
const myObject = {
  a: 1,
  myMethod: function () {
    const a = 5
    console.log(this) // {a: 1, myMethod: ƒ}
    console.log(this.a) // 1
    btn.addEventListener('click', () => {
      this.disabled = true
      const a = 10
      console.log(this) // {a: 1, disabled: true, myMethod: ƒ}
      console.log(this.a) // 1
    })
  },
}

myObject.myMethod()
// {a: 1, myMethod: ƒ}
// {a: 1, disabled: true, myMethod: ƒ}
// {a: 1, disabled: true, myMethod: ƒ}
```
`example2`からはちょっと変なことが起こってると見えるかもしれません。

ここからは直接引用できる文章がなかなか見つからないので、自分の考察と合わせて解釈していきたいと思います。
まず`myMethod: function () {...}`という無名関数の`this`を注目していただきたいと思います。
ここは一般の関数の[ Object literals {this} ](https://qiita.com/hu-yu/items/74529effd585de1d70ca#default-this-context--object-literalsthis)とつながって、つまり無名関数は`myObject`オブジェクトというスコープに参照しているということです。

中の`btn.addEventListener('click', () => {...})`のほうはアロー関数を使って、アロー関数自体は`this`持たないため **一番近いスコープに帰属する（継承する）** ので、`myMethod`と同じ`myObject`のスコープに格納されました。

なので`myObject.myMethod()`と、アロー関数使用した`addEventListener()`と、`this.a`は`myObject.a`に指していて、`addEventListener()`の`this.disabled = true`も`myObject`のプロパティになりました。

アロー関数の特性を利用して`setTimeout()`のコールバックのスコープを`myObject`に変えることもできる。
```jsx
// example3
const myObject = {
  a: 1,
  myMethod: function () {
    console.log(this) // {a: 1, myMethod: ƒ}
    btn.addEventListener('click', () => {
      this.disabled = true
      console.log(this) // {a: 1, disabled: true, myMethod: ƒ}

      setTimeout(() => {
        this.disabled = false
        console.log(this) // {a: 1, disabled: false, myMethod: ƒ}
      }, 3000)
      console.log(this) // {a: 1, disabled: true, myMethod: ƒ}
    })
  }
}

myObject.myMethod()
// {a: 1, myMethod: ƒ}
// {a: 1, disabled: true, myMethod: ƒ}
// {a: 1, disabled: true, myMethod: ƒ}
// {a: 1, disabled: false, myMethod: ƒ}
```
`bind()`を使ってもいいです。
```jsx
// example4
const myObject = {
  a: 1,
  myMethod: function () {
    console.log(this) // {a: 1, myMethod: ƒ}
    btn.addEventListener('click', () => {
      this.disabled = true
      console.log(this) // {a: 1, disabled: true, myMethod: ƒ}

      setTimeout(function () {
        this.disabled = false
        console.log(this) // {a: 1, disabled: false, myMethod: ƒ}
      }.bind(this), 3000) // this === myObject
      console.log(this) // {a: 1, disabled: true, myMethod: ƒ}
    })
  }
}
myObject.myMethod()
```
しかしいったん`addEventListener()`のスコープが変わってしまえば、`setTimeout()`も変わっていく。
```jsx
const myObject = {
  a: 1,
  myMethod: function () {
    console.log(this) // {a: 1, myMethod: ƒ}
    btn.addEventListener('click', function () {
      this.disabled = true
      console.log(this) // <button class="btn" disabled>Add New Column</button>

      setTimeout(() => {
        this.disabled = false
        console.log(this) // <button class="btn">Add New Column</button>
      }, 3000)
      console.log(this) // <button class="btn" disabled>Add New Column</button>
    })
  }
}
myObject.myMethod()
```
`addEventListener()`どうして`window`に戻るのではなく、`btn`に変わったんだろう。自分の解釈では、`btn`もオブジェクトなので、いま`btn`は**global scope**ではなく、[ Object literals {this} ](https://qiita.com/drafts/74529effd585de1d70ca/edit#default-this-context--object-literalsthis)の多重スコープのなかにあるので、`this`の指向は自分の一個上の親と直結しているわけです。**（Implicit binding）**
（`myObject.myMethod.btn.addEventListener()`、こんな感じです。

そして`setTimeout()`は`addEventListener()`と違い、アロー関数も`bind()`も使わなかったら常に`window`に指している。


# 結論
オブジェクトの中にある暗黙状態の`this`は自分の一個上の親に指している。
`call()`と`apply()`は一時に`this`コンテキストを変えることができ、戻り値を別のメソッドに使わせる。
`bind()`は永久的に`this`コンテキストを変え、ほかの関数の土台を作れる。
`new`のコンテキストは常に新しいイスタンス（つまり新しい自分自身）に指している。

アロー関数は一個上の親のスコープを継承するので、親のスコープを注意すべきです。
**WebAPIs**のコンテキストは常に`window`を指しているが、`bind()`やアロー関数でコンテキストを変えることができる。

# 参考資料まとめ
[Understanding "This" in JavaScript | Codementor](https://www.codementor.io/@dariogarciamoya/understanding--this--in-javascript-du1084lyn)
[Understanding "this" in javascript with arrow functions | Codementor](https://www.codementor.io/@dariogarciamoya/understanding-this-in-javascript-with-arrow-functions-gcpjwfyuc)
[Anomalies in JavaScript arrow functions](https://blog.logrocket.com/anomalies-in-javascript-arrow-functions/#:~:text=Arrow%20functions%20cannot%20be%20used,result%2C%20bindings%20such%20as%20new.)