イベントハンドラを登録するコンストラクタ関数があります。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', function () {
alert(this.data);
});
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
しかし、コールバック内で作成されたオブジェクトのdata
プロパティにアクセスすることができません。これは、this
が作成されたオブジェクトではなく、別のオブジェクトを参照しているように見えます。
また、無名関数ではなく、オブジェクトメソッドを使ってみました。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', this.alert);
}
MyConstructor.prototype.alert = function() {
alert(this.name);
};
を使ってみましたが、同じ問題が発生しました。
どうすれば正しいオブジェクトにアクセスできますか?
this
について知っておくべきことthis` (別名 "the context") は各関数内の特別なキーワードで、その値は関数がどのように呼び出されたかにのみ依存し、どのように/いつ/どこで定義されたかには依存しません。また、他の変数のようにレキシカルスコープの影響を受けません(矢印関数を除く。以下に例を示します。
function foo() {
console.log(this);
}
// normal function call
foo(); // `this` will refer to `window`
// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`
// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`
this
の参照の仕方this
実際には、this
にアクセスしたいのではなく、this
が参照している*オブジェクトにアクセスしたいのです。そのため、単純にそのオブジェクトを参照する新しい変数を作成するのが簡単な解決方法です。この変数にはどんな名前でもいいのですが、一般的なのは self
や that
です。
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
selfは通常の変数なので、レキシカルスコープのルールに従っており、コールバックの内部でアクセス可能です。また、コールバック自体の
this` の値にもアクセスできるという利点があります。
this
を明示的に設定する - パート1コールバックのthis
の値は自動的に設定されるので、コントロールできないように見えるかもしれませんが、実はそうではありません。
すべての関数には.bind
[docs]というメソッドがあり、this
を値に束縛した新しい関数を返します。この関数は、あなたが .bind
を呼び出したときのものとまったく同じ動作をしますが、this
はあなたが設定したものです。この関数がいつどのように呼ばれようとも、this
は常に渡された値を参照します。
function MyConstructor(data, transport) {
this.data = data;
var boundFunction = (function() { // parenthesis are not necessary
alert(this.data); // but might improve readability
}).bind(this); // <- here we are calling `.bind()`
transport.on('data', boundFunction);
}
このケースでは、コールバックの this
を MyConstructor
の this
の値にバインドしています。
注意 jQueryのコンテキストをバインドする際には、代わりにjQuery.proxy
[docs]を使用してください。このようにする理由は、イベントコールバックのバインドを解除する際に、関数への参照を保存する必要がないためです。
ECMAScript 6 では、ラムダ関数と考えられる arrow 関数 が導入されています。これらの関数は、独自の this
バインディングを持ちません。代わりに、this
は通常の変数のようにスコープ内で検索されます。つまり、.bind
を呼び出す必要はありません。さらに詳しい情報はMDNドキュメントを参照してください。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
this
を設定する - パート 2コールバックを受け付ける関数/メソッドの中には、コールバックの this
が参照する値を受け付けるものがあります。これは基本的には自分でバインドするのと同じですが、関数やメソッドが代わりにやってくれます。Array#map` [docs]はそのようなメソッドです。そのシグネチャーは
array.map(callback[, thisArg])
第一引数はコールバックで、第二引数はthis
が参照すべき値です。ここでは意図的な例を示します。
var arr = [1, 2, 3];
var obj = {multiplier: 42};
var new_arr = arr.map(function(v) {
return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument
Note: this
に値を渡すことができるかどうかは、通常、その関数/メソッドのドキュメントに記載されています。例えば、jQuery's $.ajax
method [docs]には、context
というオプションが記述されています。
このオブジェクトは、Ajax関連のすべてのコールバックのコンテキストになります。
共通の問題: オブジェクトのメソッドをコールバック/イベントハンドラとして使用する
この問題のもう一つの一般的な現れは、オブジェクトメソッドがコールバックやイベントハンドラとして使われている場合です。JavaScriptでは関数は第一級市民であり、"method"という言葉は、オブジェクトのプロパティの値を表す関数の口語的な表現に過ぎません。しかし、その関数は、その"containing"オブジェクトへの特定のリンクを持っていません。 次のような例を考えてみましょう。
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = function() {
console.log(this.data);
};
関数 this.method
がクリックイベントハンドラとして割り当てられていますが、document.body
がクリックされると、ログに記録される値は undefined
となります。これは、イベントハンドラ内では this
が document.body
を参照しており、Foo
のインスタンスを参照していないためです。
最初に述べたように、this
が何を参照するかは、その関数がどのように呼び出されるかに依存し、どのように**定義されるかには依存しません。
以下のようなコードであれば、関数がオブジェクトへの暗黙の参照を持っていないことがより明らかになるかもしれません。
function method() {
console.log(this.data);
}
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = method;
解決策は上述のものと同じです。可能であれば、.bind
を使用して、this
を特定の値に明示的にバインドしてください。
document.body.onclick = this.method.bind(this);
無名関数をコールバック/イベントハンドラとして使用して、オブジェクトの"method"として関数を明示的に呼び出し、オブジェクト(this
)を別の変数に代入します。
var self = this;
document.body.onclick = function() {
self.method();
};
または、矢印関数を使用します。
document.body.onclick = () => this.method();
それは、メソッドを呼び出すという魔法の構文にあります。
object.property();
オブジェクトからプロパティを取得して一度に呼び出す場合、オブジェクトがメソッドのコンテキストになります。同じメソッドを別々の手順で呼び出した場合は、代わりにグローバルスコープ(ウィンドウ)がコンテキストになります。
var f = object.property;
f();
メソッドの参照を取得すると、それはもうオブジェクトに添付されたものではなく、ただの関数への参照になります。コールバックとして使用するために参照を取得する場合も同様です。
this.saveNextLevelData(this.setAll);
ここでコンテキストを関数にバインドすることになります。
this.saveNextLevelData(this.setAll.bind(this));
jQuery を使用している場合は、bind
がすべてのブラウザでサポートされているわけではないので、代わりに $.proxy
メソッドを使用する必要があります。
this.saveNextLevelData($.proxy(this.setAll, this));
context"という用語は、thisで参照されるオブジェクトを指すのに使われることがあります。この用語は意味的にも技術的にもECMAScript's thisに適合していないので、使用は不適切です。
"Context"とは、意味を付加する何かを取り巻く状況や、追加の意味を与えるいくつかの前後の情報を意味します。ECMAScriptでは、execution contextという用語が使われており、実行中のコードのスコープ内にあるすべてのパラメータ、スコープ、およびthisを指します。
これはECMA-262セクション10.4.2に示されています。
呼び出し元の実行コンテキストのThisBindingと同じ値を設定する。 呼び出し実行コンテキスト
これは、this が実行コンテキストの一部であることを明確に示しています。
実行コンテキストは、実行中のコードに意味を与える周囲の情報を提供します。実行コンテキストには、thisBindingだけでなく、もっと多くの情報が含まれます。
つまり、thisの値は"context"ではなく、実行コンテキストの一部分に過ぎません。これは本質的にローカル変数であり、呼び出しによって任意のオブジェクトに設定することができ、ストリクトモードでは任意の値を設定することができます。