JavaScript の this を理解する多分一番分かりやすい説明


JavaScript の `this` は、(他のプログラム言語から見ると) ちょっと面白い挙動に見えることがあります。

先日、この `this` の挙動について、会社の同僚が説明してくれたのですが、これまで聞いた説明の中で一番分かりやすいと感じたので、頑張って日本語で説明してみます。

分かりにくかったら、多分それは私の技量不足。

## `this` と `function` の関係

`function` が基準スコープになるのがまず一点。

その `function` をどう呼ぶかで変わるのかがもう一点。

それを踏まえて……

**`this` は `function` を呼んだ時の `.` の前についているオブジェクトを指している**

と理解できるというのが、同僚の説明でした。

`.` が省略された場合はグローバルオブジェクトになります (non-strict モード時)。 strict モードでは `undefined` になります。（<span class="note" style="color:#ddd">@ryo511 さん、ご指摘感謝</span>）

![js_this_and_func.png](https://qiita-image-store.s3.amazonaws.com/0/17936/3827a8cc-c913-1f77-8a43-a97feac7300f.png "js_this_and_func.png")

### サンプルコード

これだけだとナンノコッチャかもしれないので、具体例を……

```javascript
function test() {
	console.log(this)
}
```

`this` の内容をコンソールに表示するだけの、なんてことない関数です。 

これをブラウザ上で呼び出すと……

```javascript
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
```

のようになります。

呼び出し時に `.` がないので、 `this` はグローバルオブジェクト、ブラウザでは `Window` オブジェクトになります (non-strict モード)。

この関数 `test` を特定のオブジェクトに結びつけます。

```javascript
function test() {
	console.log(this)
}
var obj = {}
obj.test = test
```

その上で、 `obj.test()` を呼び出すと、 `this` は `obj` になります。

```javascript
obj.test() // => {test: ƒ}
```

関数 `test` を呼び出す際、 `.` がついていて、 `obj.` となっているので、関数 `test` の中では `this` = `obj` となるという訳です。

逆に、あるオブジェクトに結びついているものをグローバルスコープで呼び出すこともできます。

```javascript
var obj = { test: function() { console.log(this) } }
var test = obj.test
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
```

`test()` は `obj` の関数 `test` を呼び出していることになりますが、呼び出し時に `.` がついていないので、グローバルオブジェクトが `this` になります (non-strict モード)。

上のサンプルコードでは、分かりやすくしていますが、例えば、`setTimeout` などで `obj.test` をコールバックとして渡すような場合でも同様のことが起きています。

## メソッドチェーン

複数の `function` を数珠つなぎで呼び出すメソッドチェーンは `.` の前が関数になりますが、この場合、その関数が返すオブジェクトを参照することになります。

関数で `return` を省略したり、 `return` 単独で呼ぶと `undefined` が返ってくるので、メソッドチェーンは利用できません。

```javascript
var obj = {
  test: function() { return this },
  alert: function(msg) { console.log(msg) }
}
var test = obj.test
obj.test().alert("hello") // => hello とコンソールに表示
test().alert("hello") // => アラート表示
```

関数 `alert` の呼び出しに `.` があって、その前に関数 `test` があります。

関数 `test` の返り値は `this` なので、その `test` の呼び出し方によって参照されるオブジェクトによって関数 `alert` の結果が変わります。

## `call` と `apply`

`call` や `apply` を使って関数を呼び出すと `.` に前につけるオブジェクトを指定することができます。

```javascript
function test() {
	console.log(this)
}
var obj = { name: "obj" }
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
test.call(obj) // => {name: "obj"}
```

`test.call(obj)` は `obj.test()` に等しくなります (ただし、 `obj` に `test` というメソッドは追加されません)。

### コンストラクタ

`function` に対して、 `new` 構文を利用して、オブジェクトを作成することができます。

これは大雑把に言ってしまえば、 `call` の変形構文と考えることができます。

例えば、以下のコード……

```javascript
var obj = new function() {
	this.name = "obj"
	console.log(this) // => {name: "obj"}
}
```

この時、 `function` が呼ばれますが、　`this` はグローバルオブジェクトでも `undefined` でもありません。

`new` を使うと、新規にオブジェクトを作成して、それに対して、 `call` を使って `function` を呼び出して、関数内部で `return this` するような動作になります。

つまり……

```javascript
var obj = function() {
	this.name = "obj"
	console.log(this) // => {name: "obj"}
	return this
}.call({})
```

と同じ動作と考えることができます。

これは無名関数の場合ですが、名前付き関数 (例えば `function Test()`) の場合、内部でもう少し追加処理が入ります。 ただし、 `this` の挙動に関しては、基本的に同様に考えることができると思います。

## `bind`

`bind` はこの `.` ルールの挙動を変化させて、強制的にあるオブジェクトと結びつけます。

```javascript
function test() {
	console.log(this)
}
var obj = { name: "obj" }
var check = test.bind(obj)
check() // => {name: "obj"}
```

関数 `check` の呼び出しには `.` がついていませんが、 `bind` されているので、呼び出し時に `obj.` が付く形となり、関数 `test` の中で `this` = `obj` になります。

## 関数の中の関数

`.` はあくまでも関数呼び出し時に参照されるものです。

```javascript
var obj = {
	test: function() {
		console.log(this) // *1
		function test() {
			console.log(this) // *2
		}
		test()
	}
}
obj.test()
```

のようなコードがあった時、

`*1` では `obj` がコンソールに表示され、 `*2` ではグローバルオブジェクト (例えば、 `Window`) が表示されます (non-strict モード時)。

`*2` の関数呼び出しに `.` がないからですね。

```javascript
var obj = {
	test: function() {
		console.log(this) // *1
		function test() {
			console.log(this) // *2
		}
		test.call(this)
	}
}
obj.test()
```

と `call` を使えば (`apply` でもいいです)、どっちも `obj` になります。

## まとめ

**`this` は `function` を呼んだ時の `.` の前についているオブジェクトを指している**

1. 関数呼び出しの `.` の前のオブジェクトが `this` になる
2. `.` を省略するとグローバルオブジェクトを参照する (non-strict モード)
	- strict モードでは `undefined` になる 
3. `call`, `apply` を使うと `.` の前のオブジェクトを指定できる (コンストラクタはこの変形型)
4. `bind` を使うと `.` の前のオブジェクトを強制できる

## (補足)アロー関数について

当記事では、あえて ES2015 から導入されたアロー関数について触れていませんでした。

アロー関数での `this` の振る舞いは (他のプログラム言語から見て) 比較的自然で分かりやすい気がします。

最初に触れたとおり

- `function` が基準スコープになる

のが JavaScript の大きな特徴です (`var` のスコープもこれに準じます)。 

そのため、`function` を入れ子にした途端 `this` の指すものが変わってきてしまうのが一番の混乱の要因です。当記事では、そこに焦点を当てて説明しています。

アロー関数は入れ子にしても、アロー関数の中で参照されている `this` が指すものは変わりません。そのアロー関数がキャプチャされたときの `this` を保持します。