【JS】ああthisよ。君は今、どのオブジェクトなのか（練習問題あり）

# はじめに
タイトルはポエム風に書いたが実際、thisが何を指すのか分からなくなることがよくある。

JavaScriptのthisとは何なのか、何を指すのか、この度色々実験して分かったことがあるので、自分の中での整理も兼ねて記事にしておこうと思う。
対象読者： JSのthisがいまいち分かっていない方〜なんとなく使っているがthisが何を指しているのかよく迷う方
nodeでなく、ブラウザでの挙動を前提とする。

※ 最後に練習問題も用意したので、腕に自信のある方はそちらを先にどうぞ

# thisの性質

単にthis、thisと言ってもそれが何を指すのかあいまいなので、ざっくりここでは以下の性質を持つオブジェクトをthisと考える。（厳密な定義は難しいのでここではしないが、これで十分だと思う）

* JSのコードの任意の場所で、 `this` 変数でアクセスできるオブジェクト（代入はできない）。
* thisが呼ばれたその状況により変化する(雑)。特に、関数内スコープ以外で変化することはなく、関数の外においては常にwindowである。
* （網羅できているわけではないが以降に説明される形で決定される。）

# そもそも、thisがあると何が嬉しいのか
そもそもthisがあると何が嬉しいのか。考えたが、やはりthisがあると便利ということなんだと思う。
関数はthisに関連する操作を行いたいことが多いので、暗黙的に利用できるオブジェクトが用意されているのは便利。これがないと、thisに相当するオブジェクトを引数で渡さないといけない（逆にその手間が許容できるならthisなどというものは使わなくて良い、はず）

# receiver
深く関連するワードとして、receiverと呼ばれる概念、オブジェクトがある。これの定義は次の項目を見ていただくとして、基本的にthisはreceiverオブジェクトを指す。つまりreceiverが何なのか分かれば、thisもすぐ分かる。というわけで、receiverが何にどうやって決まるのかをこの後は見ていく。難しくない。

## JavaScriptのreceiverとは
厳密な定義は違うだろうが、僕なりにJSのreceiverは次のようなものであると考えた。

〜定義〜
B()の関数呼び出しに対するreceiverは次のようにして得られるAである。

* `A.B()`の形にできるときの、オブジェクトA。このときBは必ずAのプロパティとなっている
* `A.B()`の形にできないとき、つまり`B()`のように単体で呼んでいるときはA=windowとする。

簡単ですね。

## receiverの具体例
さっそく具体例を見る。
A.B() => 関数呼び出しのreceiverは、A
B() => 関数呼び出しのreceiverは、window

これを使って考えていける。以下、Aをreceiver、Bを呼び出している関数とする。

## X.Y.Z()の場合。
Z()の関数呼び出しは、「A=X.Y、B=Z」

## X.Y(1)(2)の場合。
引数1の関数呼び出しは、「A=X、B=Y」
引数2の関数呼び出しは、「A=window、B=X.Y(1)」

## C(1)(2).D(3)(4)の場合。（練習問題4を参照）
引数1の関数呼び出しは、「A=window、B=C」
引数2の関数呼び出しは、「A=window、B=C(1)」
引数3の関数呼び出しは、「A=C(1)(2)、B=D」
引数4の関数呼び出しは、「A=window、B=C(1)(2).D(3)」

## receiverという名前について
ところで例でのAはどちらかというとBを呼び出している主体に見えるので、receiverという呼び方は不適切なのではないか(caller?sender?などの方が正しいのではないか)という風に思ってしまいがちだが、そうではない。
オブジェクト指向の考え方ではこの場合、親オブジェクト(この場合、window)がAオブジェクトにBというメッセージを送っていると考える。このときのメッセージを改めて書くと、

* 送り主(sender): この関数を呼び出したオブジェクト(window)
* 送り先(receiver): `A` オブジェクト
* メッセージの内容(message): `B(引数があれば引数も)`

なのでAがreceiverとなる。(receiver, sender, messageなどはオブジェクト指向という見方からコードを見たとき限定の言葉の選び方な気がするので、JavaScriptをOOP以外の見方で見る場合には別のワードで表現しないといけない気もするが、深入りできないのでしない。)

# thisの挙動を見ていく

receiverが何であるかをただ追えば良い。

```javascript
const obj = {}
obj.func = function() { console.log(this) } // objのメソッドとしてfunc関数を定義
/*
あるいは、
const obj = {
  func: function() { console.log(this) }
}
*/

obj.func() // obj(A.B()の形。receiverがobjなので)

const context = {}
context.func = obj.func
context.func() // context(A.B()の形。receiverがcontextなので。)

const assingedToGlobalVar = obj.func
assingedToGlobalVar() // window(B()の形。receiverがwindow。同じ理由。以下bindの項までthis=receiverである)
```
B()の形の場合receiverはwindowとなる。呼び出し時におけるthisは関係ない。(threeFuncs内部のfunc()がwindowを表示している)

```javascript
const func = function() { console.log(this) } // window下の変数としてfunc関数を定義
func() // window

const context = {}
const subContext = { func }

context.threeFuncs = function() {
  console.log(this) // context
  subContext.func() // subContext
  func() // window
}
context.threeFuncs()
```

これを利用すれば、`任意の関数` を `任意のオブジェクト（objとする）にプロパティとして代入し、objをreceiverとして呼ぶ` ことにより、`thisをobjに変えて実行できる` （変えることができてしまう）

もちろんそういった用途で関数を用意し、利用時にthisを決めたいことも多い。ただし、そうではなくthisをあらかじめ固定しておきたいときもある。そういったときは、`Function.prototype.bind`を利用する。bindは、thisが指すオブジェクトを固定することができる。
具体的には、その関数内のthisを、bindの引数オブジェクト固定したfunctionを新たに生成して返す。

```javascript
const func = function() { console.log(this) }
const context = {}
const subContext = {}

// bindした結果の関数をcontextのpropertyに指定する
context.contextIsWindowFunc = func.bind(this) // グローバルでのthisはwindow
context.contextIsSubContextFunc = func.bind(subContext) // thisをsubContextに固定
context.contextIsContextFunc = func

// contextにpropertyとして指定した関数たちを呼ぶ
context.contextIsWindowFunc() // window(receiverはcontext。ここに来てreceiver != thisになった)
context.contextIsSubContextFunc() // subContext(receiverはcontextなので、receiver != this)
context.contextIsContextFunc() // context
```

また、ES6で導入されたアロー関数は暗黙的に、関数が定義されたスコープにおけるthisにbindした関数を生成する。

```javascript
// グローバルなスコープでのthisは必ずwindowなので、thisはwindowに固定
const func = () => {
  console.log(this)
}

const context = { func }
context.func() // window
```

```javascript
const context = {
  func: function() {
    // 実行時にこのスコープのthisは決まるので、ここで定義しているarrow関数のthisも実行時に決まる
    const f = () => { console.log(this) }
    f()
  }
}

context.func() // context
subContext = {}
const func = context.func
func() // window
```

# まとめ
thisが何を指すのか分からなくなりがちだが、bindなどされていない場合にはreceiverと考えて良い。
なので、receiverの定義にしたがってreceiverを見つけ出す。上に書いた定義をよく読む（大事）
bindを利用したときはbindの引数オブジェクトにthisが固定される（固定された関数が新たに作られる）。
アロー関数は便利だが、暗黙的にbindしているので注意して使う。
ここでは紹介しなかったが、applyなど呼び出し時にthisオブジェクトを指定して関数を呼び出すこともできるが、この時の挙動はあらかじめbindしていた場合と同じように考えれば問題ない。

良ければいいねをお願いします。
また、追加の練習問題と回答も募集しています。コメント欄より教えてください。

# 練習問題
練習問題を用意したので、ログに吐かれるthisがどれを指すのか当ててください。回答、解説は下にあります。

問題1.

```javascript
const obj = {
  func: function() {
    const f = function(){ console.log(this) }
    console.log(this)
    f()
  }
}

obj.func()
```

問題2.

```javascript
const a = {
  b: {
    c: this
  }
}

console.log(a.b.c)
```

問題3.

```javascript
const a = {
  b: function (){
    return {
      c: {
        d: this
      }
    }
  }
}

console.log(a.b().c.d)
```

問題4.

```javascript
const a = function() {
  console.log(this);
  return function () {
    console.log(this);
    const b = {
      c: function() {
        console.log(this)
        return function() {
          console.log(this)
        }
      }
    }
    return b
  };
};

a()().c()()
```

問題5.

```javascript
const func = function() {
  return function () {
    const c = {
      a: function () {
        console.log(this);
      },
      b: () => {
        console.log(this);
      }
    };
    return c
  };
};

func()().a();
func()().b();
```


----
ここから回答

----
# 練習問題の回答

問題1.（回答）

```javascript
const obj = {
  func: function() {
    const f = function(){ console.log(this) }
    console.log(this) // obj
    f() // window
  }
}
obj.func()
```
〜解説〜
obj.func()なので、func()内でのthisはreceiverであるobj
しかし、f()なので、f()内でのthisはwindow


問題2.（回答）

```javascript
const a = {
  b: {
    c: this
  }
}

console.log(a.b.c) // window
```
〜解説〜
オブジェクトリテラルにおいて、valueは再帰的に即時評価される
その間、関数呼び出しは発生しないのでthisは呼び出したタイミングでのthis、すなわちwindow


問題3.（回答）

```javascript
const a = {
  b: function (){
    return {
      c: {
        d: this
      }
    }
  }
}

console.log(a.b().c.d) // a
```
〜解説〜
a.b().c.dなので、b()内でのthisはreceiverであるa
オブジェクトリテラルにおいて、valueは再帰的に即時評価されるのでthisはa

問題4.(回答)

```javascript
const a = function() {
  console.log(this); // window
  return function () {
    console.log(this); // window
    const b = {
      c: function() {
        console.log(this) // b
        return function() {
          console.log(this) // window
        }
      }
    }
    return b
  };
};

a()().c()()
```
〜解説〜
Receiverの定義の具体例を参照
問題5.(回答)

```javascript
const func = function() {
  return function () {
    const c = {
      a: function () {
        console.log(this);
      },
      b: () => {
        console.log(this);
      }
    };
    return c
  };
};

func()().a(); // c
func()().b(); // window
```

〜解説〜
func()()はcオブジェクトを返す。
c.a()になるので、aはcを表示する。
cオブジェクトが初期化されたタイミングはfunc()()のタイミング、つまりA=window、B=func()のタイミングなので、this=windowである。
このとき関数bはwindowにbindされるため、c.b()はwindowを表示する