JavaScriptのthisとは
thisは、JavaScriptで最も広く使われているキーワードの1つでありながら、混乱を招いているキーワードでもあります。
thisは特定のオブジェクトを指します。そのオブジェクトがどれなのかは、thisキーワードを含む関数がどのように呼び出されているかに依存します。
次の例を見て、結果はどうなると思いますか?
var myVar = 100;
function WhoIsThis() {
var myVar = 200;
console.log(myVar); // 200
console.log(this.myVar); // 100
}
WhoIsThis(); // inferred as window.WhoIsThis()
var obj = new WhoIsThis();
console.log(obj.myVar);
このキーワードで参照されるオブジェクトを知るには、以下の4つのルールが適用されます。
- グローバルスコープ
- オブジェクトのメソッド
- call()またはapply()メソッド
- bind()メソッド
グローバルスコープについて
thisキーワードを含む関数がグローバルスコープから呼び出された場合、this はウィンドウオブジェクトを指すことになります。
グローバルスコープとローカルスコープについては、こちらをご覧ください。
var myVar = 1000;
function WhoIsThis() {
var myVar = 2000;
console.log("myVar = " + myVar); // 200
console.log("this.myVar = " + this.myVar); // 1000
}
WhoIsThis(); // window.WhoIsThis()として推論される
上記の例では、関数WhoIsThis()がグローバルスコープから呼び出されています。
グローバルスコープとは、windowオブジェクトのコンテキスト内という意味です。オプションでwindow.WhoIsThis()のように呼び出すことができます。
つまり、上記の例では、WhoIsThis()関数内のこのキーワードは、windowオブジェクトを参照することになります。
したがって、this.myVarは100を返します。しかし、thisを付けずにmyVarにアクセスすると、WhoIsThis()関数で定義したローカル変数myVarが参照されます。
thisは内部関数で使われていてもグローバルなウィンドウオブジェクトを指します。
次のような例を考えてみましょう。
var myVar = 100;
function SomeFunction() {
function WhoIsThis() {
var myVar = 200;
console.log("myVar = " + myVar); // 200
console.log("this.myVar = " + this.myVar); // 100
}
WhoIsThis();
}
SomeFunction();
もしthisがグローバル関数内でドット記法やwindowを使わずに呼び出された場合、thisはグローバルオブジェクト(デフォルトのwindowオブジェクト)を参照することになります。
オブジェクトのメソッド内のthis
newキーワードを使って関数のオブジェクトを作成することができます。
つまり、new キーワードを使って関数のオブジェクトを作成すると、そのオブジェクトを指し示すことになります。
var myVar = 100;
function WhoIsThis() {
this.myVar = 200;
}
var obj1 = new WhoIsThis();
var obj2 = new WhoIsThis();
obj2.myVar = 300;
console.log(obj1.myVar); // 200
console.log(obj2.myVar); // 300
上記の例では、obj1インスタンスの場合はobj1を指し、obj2インスタンスの場合はobj2を指します。
JavaScriptでは、ドット記法を用いて、プロパティを動的にオブジェクトにアタッチすることができます。したがって、myVarは両方のインスタンスのプロパティとなり、それぞれはmyVarの個別のコピーを持つことになります。
以下のコードをご覧ください。
var myVar = 100;
function WhoIsThis() {
this.myVar = 200;
this.display = function(){
var myVar = 300;
console.log("myVar = " + myVar); // 300
console.log("this.myVar = " + this.myVar); // 200
};
}
var obj = new WhoIsThis();
obj.display();
上記の例では、objは2つのプロパティmyVarとdisplayを持つことになり、displayは関数式です。つまり、 display() メソッドのthisはobj.display() を呼び出すときのobj を指し示すわけです。
thisは、以下のように、オブジェクト・リテラルを使ってオブジェクトを作成した場合も同じように動作します。
var myVar = 100;
var obj = {
myVar : 300,
whoIsThis: function(){
var myVar = 200;
console.log(myVar); // 200
console.log(this.myVar); // 300
}
};
obj.whoIsThis();
call()メソッドとapply()メソッド
JavaScriptでは、以下のように()演算子やcall()、apply()メソッドを使って関数を呼び出すことができる。
function WhoIsThis() {
console.log('Hi');
}
WhoIsThis();
WhoIsThis.call();
WhoIsThis.apply();
上記の例では、WhoIsThis(), WhoIsThis.call(), WhoIsThis.apply() は、同じように関数を実行します。
call() と apply() の主な目的は、関数がグローバルスコープで呼び出されているか、オブジェクトのメソッドとして呼び出されているかに関係なく、関数内のthisのコンテキストを設定することです。
call()とapply()の最初のパラメータとして、呼び出す関数内のthisが指すべきオブジェクトを渡すことができます。
次の例は、call()とapply()を実演しています。
var myVar = 100;
function WhoIsThis() {
console.log(this.myVar);
}
var obj1 = { myVar : 200 , whoIsThis: WhoIsThis };
var obj2 = { myVar : 300 , whoIsThis: WhoIsThis };
WhoIsThis(); // 'this' will point to window object
WhoIsThis.call(obj1); // 'this' will point to obj1
WhoIsThis.apply(obj2); // 'this' will point to obj2
obj1.whoIsThis.call(window); // 'this' will point to window object
WhoIsThis.apply(obj2); // 'this' will point to obj2
上の例でわかるように、関数 WhoIsThis が () 演算子を使って呼び出された場合 (WhoIsThis() のように)、関数内のこれはルールに従い、ウィンドウ・オブジェクトを参照することになります。しかし、WhoIsThisがcall()およびapply()メソッドを使用して呼び出された場合、これは関数がどのように呼び出されるかに関係なく、最初のパラメータとして渡されるオブジェクトを参照しています。
したがって、WhoIsThis.call(obj1)として関数が呼び出された場合、これはobj1を指すことになります。同様に、WhoIsThis.apply(obj2)のように関数が呼び出されたとき、これはobj2を指します。
bind()メソッドについて
bind()メソッドは、ECMAScript 5から導入されたメソッドです。これは、関数が呼び出されたときに ‘this’ のコンテキストを指定されたオブジェクトに設定するために使用されます。
bind()メソッドは、通常、コールバック関数のthisのコンテキストを設定するのに便利です。次のような例を考えてみましょう。
var myVar = 100;
function SomeFunction(callback) {
var myVar = 200;
callback();
};
var obj = {
myVar: 300,
WhoIsThis : function() {
console.log("'this' points to " + this + ", myVar = " + this.myVar);
}
};
SomeFunction(obj.WhoIsThis);
SomeFunction(obj.WhoIsThis.bind(obj));
上記の例では、SomeFunction()のパラメータとしてobj.WhoIsThisを渡した場合、objの代わりにグローバルウィンドウオブジェクトを指します。
これは、JavaScriptエンジンによってobj.WhoIsThis()がグローバル関数として実行されるからです。
この問題は、bind()メソッドで明示的にthisの値を設定することで解決できます。
つまり、SomeFunction(obj.WhoIsThis.bind(obj))とすれば、obj.WhoIsThis.bind(obj)を指定して、objにthisを設定することができるのです。
優先順位
そこで、このキーワードがどのオブジェクトを指しているかを判断するために、以下の4つのルールを適用します。優先順位は以下の通りです。
- bind()
- call() と apply()
- オブジェクトメソッド
- グローバルスコープ
まず、bind()を使ってコールバック関数として関数が呼び出されているかどうかをチェックします。
そうでないなら、関数が call() や apply() で呼び出されているかどうか、パラメータを付けてチェックしてください。
さらにそうでなければ、関数がオブジェクト関数として呼び出されているかどうかをチェックします。
その他としては、関数がグローバルスコープでドット表記なしで呼び出されているか、ウィンドウオブジェクトを使用して呼び出されているかをチェックしてください。
このように、関数内部で「this」がどのオブジェクトを指しているかを知るためには、これらの簡単なルールを利用します。