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

Nov 29 2013

イベントハンドラーを登録するコンストラクター関数があります。

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

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

どうすれば正しいオブジェクトにアクセスできますか?

回答

1910 FelixKling Nov 29 2013 at 13:13

あなたが知っておくべきこと 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特にアクセスしたくはありませんが、それが参照するオブジェクトです。そのため、簡単な解決策は、そのオブジェクトも参照する新しい変数を作成することです。変数には任意の名前を持つことができますが、一般的なものであるselfthat

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

この場合、コールバックthisMyConstructor'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();
225 MohanDere Aug 13 2016 at 17:26

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

  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コンテキスト/これへの参照を別の変数内に格納します

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);
  });
}
59 Guffa May 21 2014 at 07:11

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

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));
34 RobG Jun 01 2014 at 07:44

「コンテキスト」の問題

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

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

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

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

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

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

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

32 Ashish Jan 30 2019 at 18:01

「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);
26 AL-zami Aug 19 2017 at 00:58

まず、のコンテキストでのキーワードの明確な理解scopeと動作を持っている必要があります。thisscope

thisscope


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

   1) Global Scope

   2) Function Scope

つまり、グローバルスコープはウィンドウオブジェクトを指します。グローバルスコープで宣言された変数はどこからでもアクセスできます。一方、関数スコープは関数の内部にあります。関数の内部で宣言された変数は通常、外部からアクセスできません。thisグローバルスコープのキーワードは、ウィンドウオブジェクトを参照します。thisinside関数はウィンドウオブジェクトも参照します。したがって、自分で選択したコンテキストを示す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つの方法をsayNameVersion1sayNameVersion2sayNameVersion3sayNameVersion4。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操作できる方法がいくつあるかを確認しましょう。thisperson

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 argumentofは、アタッチされて呼び出される関数内callで使用されます。thiscall

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)
20 DattaChanewad Nov 17 2017 at 21:32

setTimeout()常にグローバルオブジェクト(ウィンドウ)で実行されるため、これをにバインドすることはできません。thisコールバック関数のコンテキストにアクセスする場合は、コールバック関数を使用bind()して次のように実行できます。

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);
13 Code_Mode Feb 18 2019 at 16:10

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

  1. の値thisは通常、関数実行コンテキストによって決定されます。
  2. グローバルスコープでthisは、グローバルオブジェクト(windowオブジェクト)を指します。
  3. いずれかの関数でstrictモードが有効になっている場合、の値はstrictモードの場合thisundefined同じになりundefinedwindowオブジェクトの代わりにグローバルオブジェクトが参照されます。
  4. ドットの前にあるオブジェクトは、このキーワードがバインドされるものです。
  5. 私たちは、と明示的にこの値を設定することができcall()bind()およびapply()
  6. ときnewキーワードは(コンストラクタ)が使用され、これは、新しいオブジェクトが作成されることにバインドされています。
  7. 矢印関数はバインドされません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);
  });
}

参照:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. 矢印-関数-vs-バインド
8 skyboyer Sep 22 2018 at 20:38

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

クラスフィールドのサポートにより 、次の方法でそれを行うことができます。

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

確かに、内部ではコンテキストをバインドするのはすべて古い良い矢印関数ですが、この形式では、明示的なバインドよりもはるかに明確に見えます。

ステージ3の提案なので、今のところ(2018年8月)処理するには、babelと適切なbabelプラグインが必要になります。

5 AndreaPuddu Aug 28 2018 at 16:10

別のアプローチは、DOM2thisイベントリスナー内でバインドするための標準的な方法であり(他の利点の中でも)常にリスナーを削除できるようにする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を参照してください。

2 WillemvanderVeen May 05 2020 at 15:48

this JSで:

thisJSのの値は、関数の定義方法ではなく、関数の呼び出し方法によって100%決定されます。'ドットルールの左側'thisによっての値を比較的簡単に見つけることができます:

  1. functionキーワードを使用して関数を作成する場合、の値thisは、呼び出される関数のドットの左側にあるオブジェクトです。
  2. ドットの左側にオブジェクトがない場合this、関数内の値は多くの場合、グローバルオブジェクト(globalノード内window、ブラウザー内)です。thisここでキーワードを使用することはお勧めしません。これは、window!のようなものを使用するよりも明確ではないためです。
  3. 矢印関数や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番目から始まる最初のログは無視します)。

  1. thisですobj2ので、ドットルールの左側の、私たちはどのように見ることができますtest1と呼ばれていますobj2.test1();obj2ドットの左側にあるため、this値です。
  2. obj2ドットの左側にありますが、メソッドtest2obj1介してバインドされbind()ます。したがって、this値はobj1です。
  3. obj2呼び出される関数のドットの左側にあります:obj2.test3()。したがってobj2、の値になりthisます。
  4. この場合:obj2.test4() obj2はドットの左側です。ただし、矢印関数には独自のthisバインディングはありません。したがって、最初にログに記録されたオブジェクトであるthis外部スコープの値にバインドmodule.exportsされます。
  5. 関数thisを使用しての値を指定することもできますcall。ここでthis、引数として目的の値を渡すことができobj2ます。この場合はそうです。
1 Md.TazbirUrRahmanBhuiyan Oct 12 2020 at 23:07

このような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
 }