Bagaimana cara mengakses `ini` yang benar di dalam callback?

Nov 29 2013

Saya memiliki fungsi konstruktor yang mendaftarkan penangan peristiwa:

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

Namun, saya tidak dapat mengakses dataproperti objek yang dibuat di dalam callback. Sepertinya thistidak mengacu pada objek yang telah dibuat tetapi pada yang lain.

Saya juga mencoba menggunakan metode objek alih-alih fungsi anonim:

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

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

tapi itu menunjukkan masalah yang sama.

Bagaimana saya bisa mengakses objek yang benar?

Jawaban

1910 FelixKling Nov 29 2013 at 13:13

Apa yang harus Anda ketahui this

this(alias "konteks") adalah kata kunci khusus di dalam setiap fungsi dan nilainya hanya bergantung pada bagaimana fungsi itu dipanggil, bukan bagaimana / kapan / di mana ia didefinisikan. Itu tidak dipengaruhi oleh lingkup leksikal seperti variabel lain (kecuali untuk fungsi panah, lihat di bawah). Berikut beberapa contohnya:

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`

Untuk mempelajari lebih lanjut this, lihat dokumentasi MDN .


Bagaimana mengacu pada yang benar this

Gunakan fungsi panah

ECMAScript 6 memperkenalkan fungsi panah , yang dapat dianggap sebagai fungsi lambda. Mereka tidak memiliki thisikatannya sendiri . Sebaliknya, thisdilihat dalam ruang lingkup seperti variabel normal. Itu artinya Anda tidak perlu menelepon .bind. Itu bukan satu-satunya perilaku khusus yang mereka miliki, silakan merujuk ke dokumentasi MDN untuk informasi lebih lanjut.

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

Jangan gunakan this

Anda sebenarnya tidak ingin mengakses thissecara khusus, tetapi objek yang dirujuknya . Itulah mengapa solusi yang mudah adalah dengan membuat variabel baru yang juga merujuk ke objek tersebut. Variabel dapat memiliki nama apa saja, tetapi yang umum adalah selfdan that.

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

Karena selfmerupakan variabel normal, ia mematuhi aturan cakupan leksikal dan dapat diakses di dalam callback. Ini juga memiliki keuntungan bahwa Anda dapat mengakses thisnilai callback itu sendiri.

Set thiscallback secara eksplisit - bagian 1

Ini mungkin terlihat seperti Anda tidak memiliki kendali atas nilai thiskarena nilainya diatur secara otomatis, tetapi sebenarnya tidak demikian.

Setiap fungsi memiliki metode .bind [docs] , yang mengembalikan fungsi baru dengan thisterikat pada sebuah nilai. Fungsi tersebut memiliki perilaku yang persis sama dengan yang Anda panggil .bind, hanya itu thisyang Anda tetapkan. Tidak peduli bagaimana atau kapan fungsi itu dipanggil, thisakan selalu mengacu pada nilai yang diteruskan.

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

Dalam kasus ini, kami mengikat callback thiske nilai MyConstructor's this.

Catatan: Saat konteks mengikat untuk jQuery, gunakan jQuery.proxy [dokumen] sebagai gantinya. Alasan untuk melakukan ini adalah agar Anda tidak perlu menyimpan referensi ke fungsi saat melepaskan callback peristiwa. jQuery menanganinya secara internal.

Set thispanggilan balik - bagian 2

Beberapa fungsi / metode yang menerima callback juga menerima nilai yang thisharus dirujuk oleh callback . Ini pada dasarnya sama dengan mengikatnya sendiri, tetapi fungsi / metode melakukannya untuk Anda. Array#map [docs] adalah metode seperti itu. Tanda tangannya adalah:

array.map(callback[, thisArg])

Argumen pertama adalah callback dan argumen kedua adalah nilai yang thisharus dirujuk. Berikut adalah contoh yang dibuat-buat:

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

Catatan: Apakah Anda dapat mengirimkan nilai untuk atau thistidak biasanya disebutkan dalam dokumentasi fungsi / metode tersebut. Misalnya, metode jQuery [docs]$.ajax menjelaskan opsi yang disebut context:

Objek ini akan dijadikan konteks semua callback terkait Ajax.


Masalah umum: Menggunakan metode objek sebagai penangan callback / event

Manifestasi umum lainnya dari masalah ini adalah ketika metode objek digunakan sebagai penangan callback / event. Fungsi adalah warga negara kelas satu dalam JavaScript dan istilah "metode" hanyalah istilah sehari-hari untuk fungsi yang merupakan nilai properti objek. Tetapi fungsi itu tidak memiliki tautan khusus ke objek "berisi" -nya.

Perhatikan contoh berikut:

function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = function() {
    console.log(this.data);
};

Fungsi this.methodini ditetapkan sebagai pengendali peristiwa klik, tetapi jika document.bodydiklik, nilai yang dicatat akan menjadi undefined, karena di dalam pengendali peristiwa, thismerujuk ke document.body, bukan instance Foo.
Seperti yang telah disebutkan di awal, apa yang thisdimaksud bergantung pada bagaimana fungsi dipanggil , bukan bagaimana didefinisikan .
Jika kodenya seperti berikut, mungkin lebih jelas bahwa fungsi tersebut tidak memiliki referensi implisit ke objek:

function method() {
    console.log(this.data);
}


function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = method;

Solusinya sama seperti yang disebutkan di atas: Jika tersedia, gunakan .binduntuk mengikat secara eksplisit thiske nilai tertentu

document.body.onclick = this.method.bind(this);

atau secara eksplisit memanggil fungsi sebagai "metode" dari objek, dengan menggunakan fungsi anonim sebagai penangan panggilan balik / kejadian dan menetapkan objek ( this) ke variabel lain:

var self = this;
document.body.onclick = function() {
    self.method();
};

atau gunakan fungsi panah:

document.body.onclick = () => this.method();
225 MohanDere Aug 13 2016 at 17:26

Berikut beberapa cara untuk mengakses konteks induk di dalam konteks anak -

  1. Anda dapat menggunakan bind()fungsi.
  2. Simpan referensi ke konteks / ini di dalam variabel lain (lihat contoh di bawah).
  3. Gunakan fungsi panah ES6 .
  4. Ubah kode / fungsi desain / arsitektur - untuk ini, Anda harus memiliki perintah atas pola desain dalam javascript.

1. Gunakan bind()fungsi

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

Jika Anda menggunakan underscore.js-http://underscorejs.org/#bind

transport.on('data', _.bind(function () {
    alert(this.data);
}, this));

2 Simpan referensi ke konteks / ini di dalam variabel lain

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

3 Fungsi panah

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}
59 Guffa May 21 2014 at 07:11

Semuanya ada dalam sintaks "ajaib" untuk memanggil metode:

object.property();

Saat Anda mendapatkan properti dari objek dan memanggilnya sekaligus, objek tersebut akan menjadi konteks untuk metode tersebut. Jika Anda memanggil metode yang sama, tetapi dalam langkah terpisah, konteksnya adalah cakupan global (jendela):

var f = object.property;
f();

Saat Anda mendapatkan referensi sebuah metode, itu tidak lagi dilampirkan ke objek, itu hanya referensi ke fungsi biasa. Hal yang sama terjadi saat Anda mendapatkan referensi untuk digunakan sebagai callback:

this.saveNextLevelData(this.setAll);

Di situlah Anda akan mengikat konteks ke fungsi:

this.saveNextLevelData(this.setAll.bind(this));

Jika Anda menggunakan jQuery, Anda harus menggunakan $.proxymetode ini, karena bindtidak didukung di semua browser:

this.saveNextLevelData($.proxy(this.setAll, this));
34 RobG Jun 01 2014 at 07:44

Masalah dengan "konteks"

Istilah "konteks" terkadang digunakan untuk merujuk pada objek yang dirujuk oleh ini . Penggunaannya tidak tepat karena tidak sesuai baik secara semantik maupun teknis dengan ECMAScript ini .

"Konteks" berarti keadaan di sekitar sesuatu yang menambah makna, atau beberapa informasi sebelum dan sesudah yang memberikan makna tambahan. Istilah "konteks" digunakan dalam ECMAScript untuk merujuk pada konteks eksekusi , yang merupakan semua parameter, cakupan, dan ini dalam cakupan beberapa kode eksekusi.

Ini ditunjukkan di ECMA-262 bagian 10.4.2 :

Setel ThisBinding ke nilai yang sama dengan ThisBinding dari konteks eksekusi panggilan

yang dengan jelas menunjukkan bahwa ini adalah bagian dari konteks eksekusi.

Konteks eksekusi memberikan informasi di sekitarnya yang menambah makna pada kode yang sedang dieksekusi. Ini mencakup lebih banyak informasi daripada hanya penjilidan ini .

Jadi nilai ini bukanlah "konteks", itu hanya salah satu bagian dari konteks eksekusi. Ini pada dasarnya adalah variabel lokal yang dapat disetel dengan panggilan ke objek apa pun dan dalam mode ketat, ke nilai apa pun.

32 Ashish Jan 30 2019 at 18:01

Anda harus tahu tentang kata kunci "ini".

Sesuai pandangan saya, Anda dapat menerapkan "ini" dalam tiga cara (Fungsi Self / Panah / Metode Bind)

A function's this keyword behaves a little differently in JavaScript compared to other languages.

It also has some differences between strict mode and non-strict mode.

In most cases, the value of this is determined by how a function is called.

It can't be set by assignment during execution, and it may be different each time the function is called.

ES5 introduced the bind() method to set the value of a function's this regardless of how it's called,

and ES2015 introduced arrow functions which don't provide their own this binding (it retains this value of the enclosing lexical context).

Method1: Self - Self is being used to maintain a reference to the original this even as the context is changing. It's a technique often used in event handlers (especially in closures).

Reference : 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: Arrow function - An arrow function expression is a syntactically compact alternative to a regular function expression,

although without its own bindings to the this, arguments, super, or new.target keywords.

Arrow function expressions are ill-suited as methods, and they cannot be used as constructors.

Reference: 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- The bind() method creates a new function that,

when called, has its this keyword set to the provided value,

with a given sequence of arguments preceding any provided when the new function is called.

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

First, you need to have a clear understanding of scope and behaviour of this keyword in the context of scope.

this & scope :


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

   1) Global Scope

   2) Function Scope

in short, global scope refers to the window object.Variables declared in a global scope are accessible from anywhere.On the other hand function scope resides inside of a function.variable declared inside a function cannot be accessed from outside world normally.this keyword in global scope refers to the window object.this inside function also refers to the window object.So this will always refer to the window until we find a way to manipulate this to indicate a context of our own choosing.

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

Different ways to manipulate this inside callback functions:

Here I have a constructor function called Person. It has a property called name and four method called sayNameVersion1,sayNameVersion2,sayNameVersion3,sayNameVersion4. All four of them has one specific task.Accept a callback and invoke it.The callback has a specific task which is to log the name property of an instance of Person constructor function.

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)

}

Now let's create an instance from person constructor and invoke different versions of sayNameVersionX ( X refers to 1,2,3,4 ) method with niceCallback to see how many ways we can manipulate the this inside callback to refer to the person instance.

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

bind :

What bind do is to create a new function with the this keyword set to the provided value.

sayNameVersion1 and sayNameVersion2 use bind to manipulate this of the callback function.

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

first one bind this with callback inside the method itself.And for the second one callback is passed with the object bound to it.

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 :

The first argument of the call method is used as this inside the function that is invoked with call attached to it.

sayNameVersion3 uses call to manipulate the this to refer to the person object that we created, instead of the window object.

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

and it is called like the following :

p1.sayNameVersion3(niceCallback)

apply :

Similar to call, first argument of apply refers to the object that will be indicated by this keyword.

sayNameVersion4 uses apply to manipulate this to refer to person object

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

and it is called like the following.Simply the callback is passed,

p1.sayNameVersion4(niceCallback)
20 DattaChanewad Nov 17 2017 at 21:32

We can not bind this to setTimeout(), as it always execute with global object (Window), if you want to access this context in the callback function then by using bind() to the callback function we can achieve as:

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

The question revolves around how this keyword behaves in javascript. this behaves differently as below,

  1. The value of this is usually determined by a function execution context.
  2. In the global scope, this refers to the global object (the window object).
  3. If strict mode is enabled for any function then the value of this will be undefined as in strict mode, global object refers to undefined in place of the window object.
  4. The object that is standing before the dot is what this keyword will be bound to.
  5. We can set the value of this explicitly with call(), bind(), and apply()
  6. When the new keyword is used (a constructor), this is bound to the new object being created.
  7. Arrow Functions don’t bind this — instead, this is bound lexically (i.e. based on the original context)

As most of the answers suggest, we can use Arrow function or bind() Method or Self var. I would quote a point about lambdas (Arrow function) from Google JavaScript Style Guide

Prefer using arrow functions over f.bind(this), and especially over goog.bind(f, this). Avoid writing const self = this. Arrow functions are particularly useful for callbacks, which sometimes pass unexpectedly additional arguments.

Google clearly recommends using lambdas rather than bind or const self = this

So the best solution would be to use lambdas as below,

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

References:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. arrow-functions-vs-bind
8 skyboyer Sep 22 2018 at 20:38

Currently there is another approach possible if classes are used in code.

With support of class fields it's possible to make it next way:

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

For sure under the hood it's all old good arrow function that bind context but in this form it looks much more clear that explicit binding.

Since it's Stage 3 Proposal you will need babel and appropriate babel plugin to process it as for now(08/2018).

5 AndreaPuddu Aug 28 2018 at 16:10

Another approach, which is the standard way since DOM2 to bind this within the event listener, that let you always remove the listener (among other benefits), is the handleEvent(evt)method from the EventListener interface:

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

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

Detailed information about using handleEvent can be found here: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38

2 WillemvanderVeen May 05 2020 at 15:48

this in JS:

The value of this in JS is 100% determined by how a function is called, and not how it is defined. We can relatively easily find the value of this by the 'left of the dot rule':

  1. When the function is created using the function keyword the value of this is the object left of the dot of the function which is called
  2. If there is no object left of the dot then the value of this inside a function is often the global object (global in node, window in browser). I wouldn't recommend using the this keyword here because it is less explicit than using something like window!
  3. There exist certain constructs like arrow functions and functions created using the Function.prototype.bind() a function that can fix the value of this. These are exceptions of the rule but are really helpful to fix the value of this.

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

Output:

Let me walk you through the outputs 1 by 1 (ignoring the first log starting from the second):

  1. this is obj2 because of the left of the dot rule, we can see how test1 is called obj2.test1();. obj2 is left of the dot and thus the this value.
  2. Even though obj2 is left of the dot, test2 is bound to obj1 via the bind() method. So the this value is obj1.
  3. obj2 is left of the dot from the function which is called: obj2.test3(). Therefore obj2 will be the value of this.
  4. In this case: obj2.test4() obj2 is left of the dot. However, arrow functions don't have their own this binding. Therefore it will bind to the this value of the outer scope which is the module.exports an object which was logged in the beginning.
  5. We can also specify the value of this by using the call function. Here we can pass in the desired this value as an argument, which is obj2 in this case.
1 Md.TazbirUrRahmanBhuiyan Oct 12 2020 at 23:07

I was facing problem with Ngx line chart xAxisTickFormatting function which was called from HTML like this: [xAxisTickFormatting]="xFormat". I was unable to access my component's variable from the function declared. This solution helped me to resolve the issue to find the correct this. Hope this helps the Ngx line chart, users.

instead of using the function like this:

xFormat (value): string {
  return value.toString() + this.oneComponentVariable; //gives wrong result 
}

Use this:

 xFormat = (value) => {
   // console.log(this);
   // now you have access to your component variables
   return value + this.oneComponentVariable
 }