コールバック内で正しい `this` にアクセスする方法

javascript callback 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 作成されたオブジェクトではなく、他の1を参照していません。

また、匿名関数ではなくオブジェクトメソッドを使ってみました。

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', this.alert);
}

MyConstructor.prototype.alert = function() {
    alert(this.name);
};

しかし、それは同じ問題を示しています。

正しいオブジェクトにアクセスするには?




Answer 1 Felix Kling


あなたはについて知っておくべき 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 を参照する方法

this 使わないで

実際には this に特にアクセスしたくないが、それが参照するオブジェクト。そのため、簡単な解決策は、そのオブジェクトも参照する新しい変数を作成することです。変数には任意の名前を付けることができますが、一般的なものは selfthat です。

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function() {
        alert(self.data);
    });
}

以来 self 通常の変数である、それはレキシカルスコープの規則に従うと、コールバックの内部にアクセス可能です。これには、コールバック自体の this 値にアクセスできるという利点もあります。

this をコールバックに明示的に設定する-パート1

this 値は自動的に設定されるため、この値を制御できないように見えるかもしれませんが、実際にはそうではありません。

すべての機能は、方法がある .bind [ドキュメント]を使用して新しい機能を返し、 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 の値に MyConstructorthis

注: jQueryのコンテキストをバインドする場合は、代わりに jQuery.proxy [docs]を使用してください。これを行う理由は、イベントコールバックをバインド解除するときに関数への参照を保存する必要がないようにするためです。jQueryはそれを内部的に処理します。

ECMAScript 6:矢印関数を使用する

ECMAScript 6では、ラムダ関数と考えることができる矢印関数が導入されています。彼らには独自の 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])

最初の引数はコールバックで、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関連のコールバックのコンテキストになります。


よくある問題:オブジェクトメソッドをコールバックイベントハンドラとして使用する

この問題のもう一つの一般的な症状は、オブジェクトメソッドがcallbackeventハンドラとして使用されている場合です。関数は 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 は、イベントハンドラー内では Foo のインスタンスではなく document.body を参照するためです。
冒頭ですでに述べたように、 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();



Answer 2 Mohan Dere


子コンテキストの中で親コンテキストにアクセスする方法がいくつかあります。

  1. bind() 関数を使用できます。
  2. contextthisへの参照を別の変数の中に格納します(以下の例を参照)。
  3. ES6の矢印関数を使用します。
  4. コード/関数の設計/アーキテクチャを変更する-このためには、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 contextthisへの参照を別の変数に格納

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);
  });
}



Answer 3 Guffa


すべてはメソッドを呼び出すという「魔法の」構文にあります。

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));



Answer 4 RobG


"文脈 "の悩み

「コンテキスト」という用語は、thisによって参照されるオブジェクトを指すために使用されることがあります。ECMAScriptのthisに意味的または技術的に適合しないため、その使用は不適切です。

「コンテキスト」とは、意味を追加する何かを取り巻く状況、または追加の意味を与える前後の情報を意味します。用語「コンテキスト」を参照するためにECMAScriptのに使用されている実行コンテキストすべてのパラメータ、範囲であり、この一部の実行コードの範囲内です。

これはECMA-262セクション10.4.2に示されています。

ThisBinding を呼び出し実行コンテキストの ThisBinding と同じ値に設定します。

これは実行コンテキストの一部であることを明確に示しています。

実行コンテキストは、実行されるコードに意味を追加する周囲の情報を提供します。thisBindingだけではなく、はるかに多くの情報が含まれています。

したがって、この値は「コンテキスト」ではなく、実行コンテキストの一部にすぎません。これは基本的にローカル変数であり、任意のオブジェクトへの呼び出しによって、厳密モードでは、任意の値に設定できます。




Answer 5 Ashish


「これ」のキーワードについて知っておくべきです。

私の見解によれば、「これ」を3つの方法で実装できます(自己/矢印機能/バインド方法)

関数のこのキーワードは、JavaScriptでは他の言語と比較して少し違った振る舞いをします。

また、厳密なモードとそうでないモードの違いもあります。

ほとんどの場合、関数がどのように呼ばれるかによって、その値が決まります。

実行中に代入で設定することはできず、関数が呼ばれるたびに異なる場合があります。

ES5では、関数の呼び出し方に関わらず、関数のthisの値を設定するbind()メソッドが導入されました。

とES2015では、独自のこのバインディングを提供しない矢印関数が導入されました(これは、包含する語彙コンテキストのこの値を保持します)。

方法1: Self-Selfは、コンテキストが変化しても、元のthisへの参照を維持するために使用されています。これは、イベントハンドラー(特にクロージャー)でよく使用される手法です。

リファレンス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()メソッドは、新しい関数を作成します。

が呼び出された場合、そのキーワードは指定された値に設定されます。

は、新しい関数が呼び出されたときに与えられた引数の前に、与えられた一連の引数を持つようになります。

Reference: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);