JavaScriptのthis
にはいくつかの種類があり、状況によって動作が変わる。
現在はおおまかに分けて5つ?
コンストラクタ呼出し
function Func1(arg1, arg2){ this.arg1 = arg1; this.arg2 = arg2; } let obj1 = new Func1('test', 123456); // obj1.arg1 => 'test' // obj1.arg2 => 123456
Function
オブジェクトのコンストラクタをnew
で呼び出してインスタンスを生成する場合。
この文脈でのthis
はインスタンス自身を指す。
メソッド呼出し
let obj = { val : 'test', func1 : function(){ console.log(this.val); // =>'test' } } obj.func1();
オブジェクトの中のメソッドからthis
を参照した場合。
メソッドの存在するオブジェクトをthis
とする。
関数呼び出し
function func1(arg1, arg2){ this.arg1 = arg1; this.arg2 = arg2; } func1('test', 123456); console.log(window.arg1); //windowオブジェクトにプロパティがセットされる。 //windowはグローバルオブジェクトのため、プロパティもグローバルになってしまう。
Function
オブジェクトをnew
をつけずにそのまま呼び出した場合。
普通に呼び出される関数の中でthis
を使用すると、Function
内にスコープが限定されず、
window
オブジェクトを参照してしまう。
Strictモードを使用した場合はエラーが発生して利用できなくなるため安全。
'use strict'; function func1(arg1, arg2){ this.arg1 = arg1; this.arg2 = arg2; } func1('test', 123456); console.log(window.arg1); //Exception: TypeError: this is undefined
関数呼び出しの注意点
let obj = { val : 'obj prop', func1 : function(){ // メソッド呼び出し // obj.val console.log(this.val); function nestedFunc(){ // 関数呼び出し // window.val console.log(this.val); } nestedFunc(); } } window.val = 'window.prop'; obj.func1();
メソッド呼出しの中で関数呼出しを行った場合でも、単体で関数呼出しを行った場合と同様にthis
はwindow
を指してしまう。
ECMASript6(ECMAScript2015)の場合は、後述するアロー関数を用いることで、this
を一意にすることができる。
それ以外の環境の場合、下記のような対処法がある。
self
let obj = { val : 'obj prop', func1 : function(){ let self = this; console.log(self.val); // =>'obj prop' function nestedFunc(){ console.log(self.val); // =>'obj prop' } nestedFunc(); } } window.val = 'window.prop'; obj.func1();
参照するべきthis
を変数に保持してしまう方法。
慣例的にself
、that
、this
などの変数名が利用される。
単純だがわかりやすい。
bind()
let obj = { val : 'obj prop', func1 : function(){ console.log(this.val); function nestedFunc(){ console.log(this.val); }; // thisをbindしたfunctionを定義 let bindFunc = nestedFunc.bind(this); bindFunc(); //またはそのまま実行 nestedFunc.bind(this)(); } } window.val = 'window.prop'; obj.func1();
参照されるべきthis
の値をbind()
で束縛した新しいFunction
オブジェクトを作成する。
作成した関数を実行することで別のものを参照しないようにする方法。
部分適用はしやすいが、少し可読性が良くない。
個人的には、どちらかと言えばself
の方が嬉しい気持ちになる。
apply
またはcall
で呼び出し時
let obj = { val : 'obj prop', } function func1(){ console.log(this.val); } func1(); // => undefined func1.call(obj); // => 'obj prop' func1.apply(obj); // => 'obj prop'
Function.prototype.apply()
とFunction.prototype.call()
は、
引数で与えられたオブジェクトをthis
としてセットして呼び出すことができる。
apply
とcall
の違い
let obj = { val : 'obj prop', } function func1(arg1, arg2){ console.log(this.val + ' ' + arg1 + ' ' + arg2); } func1.call(obj, 'test', 'args'); // => 'obj prop test args' func1.apply(obj, ['test', 'args']); // => 'obj prop test args'
両方共第一引数はthis
にセットする値だが、下記の点が異なる。
* call
は第二引数以降に与えた引数が、呼び出すオブジェクトに引数として渡される。
* apply
は第二引数に渡した配列の中身が引数として渡される。
アロー関数で呼び出し時
let obj = { val : 'obj prop', func1 : function(){ console.log(this.val); // アロー関数は、外側のthisを自身のthisとして扱うため、 // func1のthis.val = obj.valを参照する。 let nestedFunc = () => { console.log(this.val); } nestedFunc(); } } window.val = 'window.prop'; obj.func1();
ECMASript6(ECMAScript2015)以降では、アロー関数という仕組みが提供されている。
アロー関数は、自分が宣言されているスコープのthis
を引き継いで関数内でthis
として扱うため、
直感に反しないthis
の扱い方をすることができる。
アロー関数の注意点
window.val = 'window val'; let func = () => { console.log(this.val); } let obj2 = {val : 2}; func(); // => windos val func.call(obj2); // => windos val func.apply(obj2); // => windos val
前述したように、アロー関数のthis
は外側のスコープのthis
を引き継いでセットされる。
アロー関数の場合、call
やapply
で呼び出してもこの前提は変わらず、this
を置き換えることが出来ない。