web-dev-qa-db-ja.com

コールバック内で正しい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);
};

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

正しいオブジェクトにアクセスする方法

1184
Felix Kling

thisについて知っておくべきこと

this(別名「コンテキスト」)は各関数内の特別なキーワードであり、その値はhow関数が定義された方法/時/場所ではなく、関数の呼び出しにのみ依存します。他の変数のような字句スコープの影響を受けません(矢印関数を除く、以下を参照)。ここではいくつかの例を示します。

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 documentation をご覧ください。


正しい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[ドキュメント] 、値にバインドされた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);
}

この場合、コールバックのthisMyConstructorthisの値にバインドしています。

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

ECMAScript 6:使用 矢印関数

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[ドキュメント] はそのような方法です。その署名は次のとおりです。

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メソッド[ドキュメント]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になります。イベントハンドラ内では、thisdocument.bodyではなくFooのインスタンス。
冒頭ですでに述べたように、thisが参照するものは、関数がdefinedではなく、calledである方法に依存します。
コードが次のようなものである場合、関数がオブジェクトへの暗黙的な参照を持たないことがより明白になる可能性があります。

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();
1602
Felix Kling

これが子コンテキスト内で親コンテキストにアクセスするいくつかの方法です -

  1. bind()関数を使うことができます。 
  2. Context/thisへの参照を別の変数内に格納します(下記の例を参照)。
  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 context/thisへの参照を別の変数の中に格納する

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);
  });
}
163
Mohan Dere

メソッドを呼び出すのは「魔法の」構文です。

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

「文脈」とのトラブル

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

"Context" は、意味を追加するもの、または追加の意味を与える前後の情報を取り巻く状況を意味します。 "context"という用語はECMAScriptで 実行コンテキスト を指すために使用されます。これは、実行コードの範囲内で、すべてのパラメータ、スコープ、および this です。

これは ECMA-262の10.4.2節に示されています

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

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

実行コンテキストは、実行されているコードに意味を追加する周囲の情報を提供します。これには、 thisBinding というより多くの情報が含まれています。

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

22
RobG

まず、scopeNAME_を明確に理解し、thisNAME_のコンテキストでscopeNAME_キーワードの動作を理解する必要があります。

thisname__&scopename__:


there are two types of scope in javascript. They are :

   1) Global Scope

   2) Function Scope

要するに、グローバルスコープはウィンドウオブジェクトを指します。グローバルスコープで宣言された変数はどこからでもアクセスできます。一方、関数スコープは関数内にあります。関数内で宣言された変数は外部から通常アクセスできません。thisNAME_グローバルスコープのキーワードはウィンドウオブジェクトを参照します。thisNAME_関数内もウィンドウオブジェクトを参照します。したがって、thisNAME_は、方法を見つけるまで常にウィンドウを参照します。 thisNAME_を操作して、自分で選択したコンテキストを示します。

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

コールバック関数内でthisname__を操作するさまざまな方法:

ここには、Personというコンストラクター関数があります。 nameNAME_というプロパティと、sayNameVersion1sayNameVersion2sayNameVersion3sayNameVersion4という4つのメソッドがあります。 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コンストラクターからインスタンスを作成し、sayNameVersionXNAME_(Xは1,2,3,4を指します)メソッドのさまざまなバージョンをniceCallbackNAME_で呼び出して、 thisNAME_インスタンスを参照するコールバック内のpersonNAME_.

var p1 = new Person('zami') // create an instance of Person constructor

bind:

バインドは、指定された値にthisNAME_キーワードを設定して新しい関数を作成します。

sayNameVersion1およびsayNameVersion2はバインドを使用して、コールバック関数のthisNAME_を操作します。

this.sayNameVersion1 = function(callback){
    callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
    callback()
}

最初のメソッドは、メソッド自体の内部でコールバックを使用してthisNAME_をバインドします。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

call:

callNAME_メソッドのfirst argumentは、thisNAME_を付けて呼び出された関数内でcallNAME_として使用されます。

sayNameVersion3callNAME_を使用してthisNAME_を操作し、ウィンドウオブジェクトではなく、作成した人物オブジェクトを参照します。

this.sayNameVersion3 = function(callback){
    callback.call(this)
}

そして、それは次のように呼び出されます:

p1.sayNameVersion3(niceCallback)

apply:

callNAME_と同様に、applyNAME_の最初の引数は、thisNAME_キーワードで示されるオブジェクトを参照します。

sayNameVersion4applyNAME_を使用してthisNAME_を操作し、人物オブジェクトを参照します

this.sayNameVersion4 = function(callback){
    callback.apply(this)
}

次のように呼び出されます。単にコールバックが渡されますが、

p1.sayNameVersion4(niceCallback)
19
AL-zami

これをsetTimeout()にバインドすることはできません。コールバック関数でthisコンテキストにアクセスしたい場合は、bind()をコールバック関数に使用することで、 global object(Window) で実行できます。

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);
15
Datta Chanewad

"this"キーワードについて知っておくべきです。  

私の見方では、3つの方法で "this"を実装することができます (自己/矢印関数/バインド方法)

関数のthisキーワードは、JavaScriptでは他の言語と比べて少し異なる動作をします。

厳密モードと非厳密モードの間にもいくつかの違いがあります。

ほとんどの場合、この値は関数の呼び出し方法によって決まります。

実行中の代入で設定することはできず、関数が呼び出されるたびに異なる可能性があります。

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

そしてES2015は独自のthisバインディングを提供しないarrow関数を導入しました(それは包含している字句コンテキストのthis値を保持します)。

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

Method2 :矢印関数 - 矢印関数式は、通常の関数式に代わる構文上コンパクトなものです。 

ただし、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);
8
ashish

DOM2 からイベントリスナー内でthisをバインドするための標準的な方法、 (他の利点もありますが)リスナー を削除させる方法は、EventListenerインターフェースからhandleEvent(evt)メソッドです。 :

var obj = {
  handleEvent(e) {
    // always true
    console.log(this === obj);
  }
};

document.body.addEventListener('click', obj);

handleEventの使用に関する詳細な情報はここにあります: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38

2
Andrea Puddu

現在、クラスがコード内で使用されている場合、別のアプローチが可能です。 

クラスフィールドをサポートすることで それは次のようにすることが可能です:

class someView {
    onSomeInputKeyUp = (event) => {
        console.log(this); // this refers to correct value
    // ....
    someInitMethod() {
        //...
        someInput.addEventListener('input', this.onSomeInputKeyUp)

確かに内部的には、文脈を束縛するのは古き良き矢印関数ですが、この形式では明示的束縛をもっと明確にしています。

Stage 3 Proposalだから、今のところ処理するにはbabelと適切な babelプラグイン が必要になるだろう(08/2018)。

2
skyboyer

質問はjavascriptでthisキーワードがどのように振る舞うかを中心に展開します。 thisは以下のように動作が異なります。

  1. この値は通常、関数実行コンテキストによって決定されます。
  2. グローバルスコープでは、これはグローバルオブジェクト(ウィンドウオブジェクト)を指します。
  3. いずれかの機能で厳密モードが有効になっている場合、厳密モードの場合と同様に、thisの値はundefinedになります。グローバルオブジェクトは、windowsオブジェクトの代わりにundefinedを参照します。
  4. ドットの前に立っているオブジェクトは、thisキーワードがバインドされるものです。
  5. Call()、bind()、およびapply()を使用して、この値を明示的に設定できます。
  6. Newキーワードが使用されている場合(コンストラクタ)、これは作成中の新しいオブジェクトにバインドされます。
  7. 矢印関数はこれを束縛しません - 代わりに、これは語彙的に束縛されます(すなわち、元の文脈に基づいて)

答えの大部分が示唆するように、 矢印関数またはbind()メソッドまたはSelf varを使用できます。私は Google JavaScript Style Guide からラムダ(矢印関数)についてのポイントを引用するでしょう

F.bind(this)より、特に goog.bind(f、this)よりも矢印関数を使用することをお勧めします。 const self = thisと書かないでください。矢印関数は、コールバックに特に役立ちます。コールバックでは、予期しない追加の引数を渡すことがあります。

バインドやconst self = thisよりもラムダを使用することをお勧めします

したがって、最善の解決策は、以下のようにラムダを使用することです。

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

参考文献:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. arrow-functions-vs-bind
0
Code_Mode