コールバック内の正しい `this`にアクセスする方法は?
イベントハンドラーを登録するコンストラクター関数があります。
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
(別名「コンテキスト」)は各関数内の特別なキーワードであり、その値は関数がどのように呼び出されたかにのみ依存し、関数がどのように/いつ/どこで定義されたかには依存しません。他の変数のような字句スコープの影響を受けません(矢印関数を除く、以下を参照)。ここではいくつかの例を示します。
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
は、MDNドキュメントをご覧ください。
正しい参照方法 this
矢印関数を使用する
ECMAScript 6では、ラムダ関数と考えることができる矢印関数が導入されました。彼らは独自のthis
拘束力を持っていません。代わりに、this
正規変数と同じようにスコープ内で検索されます。つまり、を呼び出す必要はありません.bind
。特別な動作はこれだけではありません。詳細については、MDNのドキュメントを参照してください。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
使用しないでください 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
'sの値にバインドしていますthis
。
注: jQueryのコンテキストをバインドする場合は、代わりにjQuery.proxy
[docs]を使用してください。これを行う理由は、イベントコールバックのバインドを解除するときに関数への参照を格納する必要がないようにするためです。jQueryはそれを内部的に処理します。
this
コールバックのセット-パート2
コールバックを受け入れる一部の関数/メソッドは、コールバックthis
が参照する必要のある値も受け入れます。これは基本的に自分でバインドするのと同じですが、関数/メソッドが自動的に行います。Array#map
[docs]はそのような方法です。その署名は次のとおりです。
array.map(callback[, thisArg])
最初の引数はコールバックであり、2番目の引数は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
注:の値を渡すことができるかどうかthis
は、通常、その関数/メソッドのドキュメントに記載されています。たとえば、jQueryの$.ajax
メソッド[docs]には、次のオプションが記述されていますcontext
。
このオブジェクトは、すべてのAjax関連のコールバックのコンテキストになります。
一般的な問題:オブジェクトメソッドをコールバック/イベントハンドラーとして使用する
この問題のもう1つの一般的な兆候は、オブジェクトメソッドがコールバック/イベントハンドラーとして使用される場合です。関数はJavaScriptの第一級市民であり、「メソッド」という用語は、オブジェクトプロパティの値である関数の単なる口語的な用語です。ただし、その関数には、その「含む」オブジェクトへの特定のリンクはありません。
次の例を考えてみましょう。
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);
または、コールバック/イベントハンドラーとして無名関数を使用し、オブジェクト(this
)を別の変数に割り当てることにより、オブジェクトの「メソッド」として関数を明示的に呼び出します。
var self = this;
document.body.onclick = function() {
self.method();
};
または矢印関数を使用します。
document.body.onclick = () => this.method();
子コンテキスト内の親コンテキストにアクセスするいくつかの方法があります-
bind()
関数を使用できます。- context / thisへの参照を別の変数内に格納します(以下の例を参照)。
- ES6矢印関数を使用します。
- コード/関数のデザイン/アーキテクチャを変更する-このためには、JavaScriptのデザインパターンをコマンドする必要があります 。
1.bind()
機能を使用する
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', ( function () {
alert(this.data);
}).bind(this) );
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
使用している場合underscore.js
- http://underscorejs.org/#bind
transport.on('data', _.bind(function () {
alert(this.data);
}, this));
2コンテキスト/これへの参照を別の変数内に格納します
function MyConstructor(data, transport) {
var self = this;
this.data = data;
transport.on('data', function() {
alert(self.data);
});
}
3矢印機能
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
それはすべて、メソッドを呼び出す「魔法の」構文にあります。
object.property();
オブジェクトからプロパティを取得して一度に呼び出すと、オブジェクトがメソッドのコンテキストになります。同じメソッドを別々のステップで呼び出す場合、コンテキストは代わりにグローバルスコープ(ウィンドウ)になります。
var f = object.property;
f();
メソッドの参照を取得すると、そのメソッドはオブジェクトにアタッチされなくなり、単なる関数への参照になります。コールバックとして使用する参照を取得した場合も同じことが起こります。
this.saveNextLevelData(this.setAll);
ここで、コンテキストを関数にバインドします。
this.saveNextLevelData(this.setAll.bind(this));
jQueryを使用している場合は、$.proxy
代わりにこのメソッドを使用する必要bind
があります。これは、すべてのブラウザーでサポートされているわけではありません。
this.saveNextLevelData($.proxy(this.setAll, this));
「コンテキスト」の問題
「コンテキスト」という用語は、これによって参照されるオブジェクトを指すために使用されることがあります。ECMAScriptのthisに意味的にも技術的にも適合しないため、その使用は不適切です。
「コンテキスト」とは、意味を追加するもの、または追加の意味を与える前後の情報を取り巻く状況を意味します。「コンテキスト」という用語は、ECMAScriptで実行コンテキストを指すために使用されます。これは、すべてのパラメーター、スコープ、および一部の実行コードのスコープ内のこれです。
これはECMA-262セクション10.4.2に示されています:
ThisBindingを呼び出し元の実行コンテキストのThisBindingと同じ値に設定します
これは、これが実行コンテキストの一部であることを明確に示しています。
実行コンテキストは、実行されているコードに意味を追加する周囲の情報を提供します。thisBindingだけでなく、はるかに多くの情報が含まれています。
だから、の値これは「文脈」ではない、それは実行コンテキストの一部に過ぎません。これは本質的にローカル変数であり、任意のオブジェクトの呼び出しによって、厳密モードで任意の値に設定できます。
「this」キーワードについて知っておく必要があります。
私の見解によると、「これ」は3つの方法で実装できます (自己/矢印関数/バインドメソッド)
関数のthisキーワードは、JavaScriptでは他の言語とは少し異なる動作をします。
また、厳密モードと非厳密モードにはいくつかの違いがあります。
ほとんどの場合、この値は関数の呼び出し方法によって決まります。
実行時の割り当てでは設定できず、関数を呼び出すたびに異なる場合があります。
ES5では、関数の呼び出し方法に関係なく、関数のthisの値を設定するbind()メソッドが導入されました。
ES2015では、独自のこのバインディングを提供しない矢印関数が導入されました(これは、囲んでいる字句コンテキストのこの値を保持します)。
方法1:自己-コンテキストが変化している場合でも、自己は元のこれへの参照を維持するために使用されています。これは、イベントハンドラー(特にクロージャー)でよく使用される手法です。
参照:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function () {
alert(self.data);
});
}
方法2:矢印関数-矢印関数式は、通常の関数式の構文的にコンパクトな代替手段です。
this、arguments、super、またはnew.targetキーワードへの独自のバインディングはありませんが。
矢印関数式はメソッドとしては不適切であり、コンストラクターとして使用することはできません。
参照:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
function MyConstructor(data, transport) {
this.data = data;
transport.on('data',()=> {
alert(this.data);
});
}
Method3:Bind- bind()メソッドは、次のような新しい関数を作成します。
呼び出されると、thisキーワードが指定された値に設定されます。
新しい関数が呼び出されたときに提供される引数の前に、指定された一連の引数があります。
参照: https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
function MyConstructor(data, transport) {
this.data = data;
transport.on('data',(function() {
alert(this.data);
}).bind(this);
まず、のコンテキストでのキーワードの明確な理解scope
と動作を持っている必要があります。this
scope
this
&scope
:
there are two types of scope in javascript. They are :
1) Global Scope
2) Function Scope
つまり、グローバルスコープはウィンドウオブジェクトを指します。グローバルスコープで宣言された変数はどこからでもアクセスできます。一方、関数スコープは関数の内部にあります。関数の内部で宣言された変数は通常、外部からアクセスできません。this
グローバルスコープのキーワードは、ウィンドウオブジェクトを参照します。this
inside関数はウィンドウオブジェクトも参照します。したがって、自分で選択したコンテキストを示すthis
ために操作する方法が見つかるまで、常にウィンドウを参照しますthis
。
--------------------------------------------------------------------------------
- -
- Global Scope -
- ( globally "this" refers to window object) -
- -
- function outer_function(callback){ -
- -
- // outer function scope -
- // inside outer function"this" keyword refers to window object - -
- callback() // "this" inside callback also refers window object -
- } -
- -
- function callback_function(){ -
- -
- // function to be passed as callback -
- -
- // here "THIS" refers to window object also -
- -
- } -
- -
- outer_function(callback_function) -
- // invoke with callback -
--------------------------------------------------------------------------------
this
内部コールバック関数を操作するさまざまな方法:
ここにPersonというコンストラクター関数があります。これは、というプロパティがあるname
と呼ばれる4つの方法をsayNameVersion1
、sayNameVersion2
、sayNameVersion3
、sayNameVersion4
。4つすべてに1つの特定のタスクがあります。コールバックを受け入れて呼び出します。コールバックには、Personコンストラクター関数のインスタンスのnameプロパティをログに記録するという特定のタスクがあります。
function Person(name){
this.name = name
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
this.sayNameVersion3 = function(callback){
callback.call(this)
}
this.sayNameVersion4 = function(callback){
callback.apply(this)
}
}
function niceCallback(){
// function to be used as callback
var parentObject = this
console.log(parentObject)
}
次に、personコンストラクターからインスタンスを作成し、さまざまなバージョンのsayNameVersionX
(Xは1,2,3,4)メソッドを呼び出して、インスタンスを参照するために内部コールバックをniceCallback
操作できる方法がいくつあるかを確認しましょう。this
person
var p1 = new Person('zami') // create an instance of Person constructor
bindは、this
キーワードを指定された値に設定して新しい関数を作成します。
sayNameVersion1
そしてsayNameVersion2
、bindを使用しthis
てコールバック関数を操作します。
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
最初の1つthis
は、メソッド自体の内部でコールバックを使用してバインドします。2つ目のコールバックは、オブジェクトをバインドして渡されます。
p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method
p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback
メソッドのfirst argument
ofは、アタッチされて呼び出される関数内call
で使用されます。this
call
sayNameVersion3
ウィンドウオブジェクトの代わりに、作成した人物オブジェクトを参照するcall
ようにを操作するthis
ために使用 します。
this.sayNameVersion3 = function(callback){
callback.call(this)
}
そしてそれは次のように呼ばれます:
p1.sayNameVersion3(niceCallback)
と同様にcall
、の最初の引数apply
は、this
キーワードで示されるオブジェクトを参照します。
sayNameVersion4
人のオブジェクトを参照するapply
ために操作this
するために使用します
this.sayNameVersion4 = function(callback){
callback.apply(this)
}
次のように呼び出されます。コールバックが渡されるだけです。
p1.sayNameVersion4(niceCallback)
setTimeout()
常にグローバルオブジェクト(ウィンドウ)で実行されるため、これをにバインドすることはできません。this
コールバック関数のコンテキストにアクセスする場合は、コールバック関数を使用bind()
して次のように実行できます。
setTimeout(function(){
this.methodName();
}.bind(this), 2000);
質問this
は、JavaScriptでキーワードがどのように動作するかを中心に展開します。this
以下のように動作が異なります。
- の値
this
は通常、関数実行コンテキストによって決定されます。 - グローバルスコープで
this
は、グローバルオブジェクト(window
オブジェクト)を指します。 - いずれかの関数でstrictモードが有効になっている場合、の値はstrictモードの場合
this
とundefined
同じになりundefined
、window
オブジェクトの代わりにグローバルオブジェクトが参照されます。 - ドットの前にあるオブジェクトは、このキーワードがバインドされるものです。
- 私たちは、と明示的にこの値を設定することができ
call()
、bind()
およびapply()
- とき
new
キーワードは(コンストラクタ)が使用され、これは、新しいオブジェクトが作成されることにバインドされています。 - 矢印関数はバインドされません
this
—代わりに、this
字句的にバインドされます(つまり、元のコンテキストに基づいて)
ほとんどの回答が示唆しているように、Arrow関数またはbind()
MethodまたはSelfvarを使用できます。Google JavaScriptスタイルガイドからラムダ(矢印関数)についてのポイントを引用します
f.bind(this)よりも、特にgoog.bind(f、this)よりも矢印関数を使用することをお勧めします。const self = thisと書くことは避けてください。矢印関数は、予期せず追加の引数を渡すことがあるコールバックに特に役立ちます。
Googleは、バインドやバインドではなくラムダを使用することを明確に推奨しています const self = this
したがって、最善の解決策は、以下のようにラムダを使用することです。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
参照:
現在、クラスがコードで使用されている場合に可能な別のアプローチがあります。
クラスフィールドのサポートにより 、次の方法でそれを行うことができます。
class someView {
onSomeInputKeyUp = (event) => {
console.log(this); // this refers to correct value
// ....
someInitMethod() {
//...
someInput.addEventListener('input', this.onSomeInputKeyUp)
確かに、内部ではコンテキストをバインドするのはすべて古い良い矢印関数ですが、この形式では、明示的なバインドよりもはるかに明確に見えます。
ステージ3の提案なので、今のところ(2018年8月)処理するには、babelと適切なbabelプラグインが必要になります。
別のアプローチは、DOM2がthis
イベントリスナー内でバインドするための標準的な方法であり、(他の利点の中でも)常にリスナーを削除できるようにするhandleEvent(evt)
もので、EventListener
インターフェイスからのメソッドです。
var obj = {
handleEvent(e) {
// always true
console.log(this === obj);
}
};
document.body.addEventListener('click', obj);
使用の詳細については、httpshandleEvent
://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38を参照してください。
this
JSで:
this
JSのの値は、関数の定義方法ではなく、関数の呼び出し方法によって100%決定されます。'ドットルールの左側'this
によっての値を比較的簡単に見つけることができます:
- functionキーワードを使用して関数を作成する場合、の値
this
は、呼び出される関数のドットの左側にあるオブジェクトです。 - ドットの左側にオブジェクトがない場合
this
、関数内の値は多くの場合、グローバルオブジェクト(global
ノード内window
、ブラウザー内)です。this
ここでキーワードを使用することはお勧めしません。これは、window
!のようなものを使用するよりも明確ではないためです。 - 矢印関数や
Function.prototype.bind()
、の値を固定できる関数を使用して作成された関数など、特定の構造が存在しますthis
。これらはルールの例外ですが、の値を修正するのに非常に役立ちますthis
。
nodeJSの例
module.exports.data = 'module data';
// This outside a function in node refers to module.exports object
console.log(this);
const obj1 = {
data: "obj1 data",
met1: function () {
console.log(this.data);
},
met2: () => {
console.log(this.data);
},
};
const obj2 = {
data: "obj2 data",
test1: function () {
console.log(this.data);
},
test2: function () {
console.log(this.data);
}.bind(obj1),
test3: obj1.met1,
test4: obj1.met2,
};
obj2.test1();
obj2.test2();
obj2.test3();
obj2.test4();
obj1.met1.call(obj2);
出力:
出力を1つずつ説明します(2番目から始まる最初のログは無視します)。
this
ですobj2
ので、ドットルールの左側の、私たちはどのように見ることができますtest1
と呼ばれていますobj2.test1();
。obj2
ドットの左側にあるため、this
値です。obj2
ドットの左側にありますが、メソッドtest2
をobj1
介してバインドされbind()
ます。したがって、this
値はobj1
です。obj2
呼び出される関数のドットの左側にあります:obj2.test3()
。したがってobj2
、の値になりthis
ます。- この場合:
obj2.test4()
obj2
はドットの左側です。ただし、矢印関数には独自のthis
バインディングはありません。したがって、最初にログに記録されたオブジェクトであるthis
外部スコープの値にバインドmodule.exports
されます。 - 関数
this
を使用しての値を指定することもできますcall
。ここでthis
、引数として目的の値を渡すことができobj2
ます。この場合はそうです。
このようなHTMLから呼び出されたNgx
折れ線グラフxAxisTickFormatting
関数で問題に直面していました:[xAxisTickFormatting]="xFormat"
。宣言された関数からコンポーネントの変数にアクセスできませんでした。この解決策は、問題を解決して正しい問題を見つけるのに役立ちました。これがNgx
折れ線グラフ、ユーザーに役立つことを願っています。
このような関数を使用する代わりに:
xFormat (value): string {
return value.toString() + this.oneComponentVariable; //gives wrong result
}
これを使って:
xFormat = (value) => {
// console.log(this);
// now you have access to your component variables
return value + this.oneComponentVariable
}