##はじめに
JavaScriptのthisの挙動について、何度学習しても忘れてしまうので自分なりに一度まとめてみました。
一見複雑に見えましたが、こうしてまとめることで少し理解が深まりました。
##基本パターン
thisの値は、関数やメソッドが呼び出される際に決まります。
そして、基本は3パターンで考えます。
- オブジェクトのメソッドとして呼び出される場合
オブジェクトのメソッドとして呼び出される場合、thisは**呼び出し元のオブジェクト**を参照します。
x = 'global'; const func = function() { console.log(this.x); } myObj = { x: 'local', myFunc: func } myObj.myFunc(); // 'local'
- 関数として呼び出される場合
単純に関数として呼び出されると、thisは**windowオブジェクト**を参照します。
x = 'global'; const func = function() { console.log(this.x); } myObj = { x: 'local', myFunc: func } func(); // 'global'
- コンストラクタ関数内で呼び出されるthis & プロトタイプメソッドのthis
これらの場合、thisは**生成されるインスタンス**(下の例ではaa)を参照します。
- コンストラクタ関数内で呼び出されるthis
class AA { constructor() { this.x = 'aa'; console.log(this.x); // 'aa' } } const aa = new AA();
- プロトタイプメソッドのthis
class AA { constructor() { this.x = 'aa'; } test() { console.log(this.x); } } const aa = new AA(); aa.test(); // 'aa'
##応用編
ここから応用編。
まずは、コールバック関数
x = 'global';
const callback = function() {
console.log(this.x);
}
const myObj = {
x: 'local',
myFunc: function(cb) {
cb();
}
}
myObj.myFunc(callback);
// 'global'
あくまでcb()という風に、関数が呼び出されているので、thisはwindowオブジェクトを指します。
コンストラクタ関数内でコールバック関数を宣言し、別のコンストラクタ関数に引数として渡す場合
class AA {
constructor() {
this.x = 'aa';
const callback = function() {
console.log(this.x);
}
const bb = new BB(callback);
}
}
class BB {
constructor(cb) {
this.x = 'bb';
this.cb = cb;
this.cb();
}
}
const aa = new AA();
// 'bb'
BBのコンストラクタ関数内では、引数のcbがインスタンスのcbに渡され(this.cb = cb)、実行されます(this.cb())。
つまり、AAのコンストラクタ関数内で定義されたcallbackは、BBのインスタンスのメソッドから呼び出されるので、thisはbbを指します。
##bindの登場
上記の例(コンストラクタ関数内でコールバック関数を宣言し、別のコンストラクタ関数に引数として渡す場合)で、bindメソッドを使ってみます。
class AA {
constructor() {
this.x = 'aa';
const callback = function() {
console.log(this.x);
}
// bindを使う
const bb = new BB(callback.bind(this));
}
}
class BB {
constructor(cb) {
this.x = 'bb';
this.cb = cb;
this.cb();
}
}
const aa = new AA();
// 'aa'
bindメソッドは、関数内で参照されるthisを指定することができます。bind(this)とすることで、この関数内のthisは、bindされた時点でのthisを参照するようになります。
上記の例では、bindメソッドを使った時点のthisはAAのインスタンスを指していますので、thisはAAのインスタンスを指します。
##アロー関数
アロー関数とは、関数「 function(引数) {処理} 」を「 (引数) => {処理} 」のように記述するものです。
アロー関数を使うと、thisは宣言時のものに固定されます。先ほどの例で具体的に確認します。
(1.オブジェクトのメソッドとして呼び出される場合)
x = 'global';
// アロー関数
// この時点でのthisに固定されるのでthisはwindowオブジェクト
const func = () => {
console.log(this.x);
}
myObj = {
x: 'local',
myFunc: func
}
myObj.myFunc();
// 'global'
(3.コンストラクタ関数内で呼び出されるthis & プロトタイプメソッドのthis)
class AA {
constructor() {
this.x = 'aa';
// アロー関数
// この時点でのthisに固定されるのでthisはAAのインスタンス
const callback = () => {
console.log(this.x);
}
const bb = new BB(callback);
}
}
class BB {
constructor(cb) {
this.x = 'bb';
this.cb = cb;
this.cb();
}
}
const aa = new AA();
// 'aa'
このように、アロー関数はまた違った挙動をしますね。
##call(), apply()
callメソッド、apllyメソッドについてです。これらのメソッドは、関数内のthisに好きなものを参照させることができます。第一引数にはthisに参照させたいもの、第二引数以降に関数の引数を渡します。
- 通常
x = 'global';
const func = function(y, z) {
console.log(this.x, y, z);
}
const myObj = {
x: 'local'
}
func('test1', 'test2');
// global test1 test2
- call()
x = 'global';
const func = function(y, z) {
console.log(this.x, y, z);
}
const myObj = {
x: 'local'
}
func.call(myObj, 'test1', 'test2');
// local test1 test2
- apply()
apply()は第二引数に配列を受け取ります。(第三引数以降はありません)
x = 'global';
const func = function(y, z) {
console.log(this.x, y, z);
}
const myObj = {
x: 'local'
}
func.apply(myObj, ['test1', 'test2']);
// local test1 test2
##イベントリスナー
addEventListenerメソッドを使った処理です。少しthisの動きが変わっています。
addEventListenerメソッドは、target.addEventListener(イベントタイプ, コールバック関数)
と記述し、「targetがクリックされた場合にコールバック関数を実行する」というように使います。
さて、addEventListenerメソッドでは、コールバック関数内のthisはtargetを参照します。
<div class="container">
<button class="btn">クリック</button>
<p>サンプル</p>
</div>
上記のようなHTMLがあるとして、
const target = document.querySelector('.btn');
target.addEventListener('click', function() {
console.log(this);
// <button class="btn">クリック</button>
});
ただし、コールバック関数のさらに内側では、通常のthisの挙動に戻ります。ややこしい・・・
const target = document.querySelector('.btn');
target.addEventListener('click', function() {
const func = function() {
console.log(this);
// window
}
func();
});
##まとめ
一通りthisの挙動について網羅できたように思います。これでしばらくthisについて考えなくて良さそうです。