Pertanyaan Bagaimana cara mengakses `this` di dalam callback?


Saya memiliki fungsi konstruktor yang mendaftarkan pengendali event:

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 data milik objek yang dibuat di dalam callback. Sepertinya this tidak merujuk ke objek yang dibuat tetapi ke yang lain.

Saya juga mencoba menggunakan metode objek daripada fungsi anonim:

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

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

tetapi itu menunjukkan masalah yang sama.

Bagaimana saya bisa mengakses objek yang benar?


914
2017-11-29 06:13


asal


Jawaban:


Apa yang seharusnya kamu ketahui this

this (alias "konteks") adalah kata kunci khusus di dalam setiap fungsi dan nilainya hanya bergantung pada bagaimana fungsi itu disebut, bukan bagaimana / kapan / di mana ia didefinisikan. Itu tidak terpengaruh oleh lingkup leksikal, seperti variabel lainnya. 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, lihatlah Dokumentasi MDN.


Bagaimana cara merujuk ke yang benar this

Jangan gunakan this

Anda sebenarnya tidak ingin mengakses this khususnya, tapi objek yang dimaksud. Karena itu solusi mudahnya adalah dengan membuat variabel baru yang juga mengacu pada objek itu. Variabel dapat memiliki nama apa pun, tetapi yang umum adalah self dan that.

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

Sejak self adalah variabel normal, mematuhi aturan lingkup leksikal dan dapat diakses di dalam callback. Ini juga memiliki kelebihan yang dapat Anda akses this nilai panggilan balik itu sendiri.

Tetapkan secara eksplisit this callback - bagian 1

Sepertinya Anda tidak memiliki kontrol atas nilai this karena nilainya diatur secara otomatis, tetapi sebenarnya bukan itu masalahnya.

Setiap fungsi memiliki metode .bind  [dokumen], yang mengembalikan fungsi baru dengan this terikat pada nilai. Fungsi ini memiliki perilaku yang persis sama dengan yang Anda panggil .bind pada, hanya itu this ditentukan oleh Anda. Tidak peduli bagaimana atau kapan fungsi itu dipanggil, this akan 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 hal ini, kami mengikat callback this untuk nilai MyConstructor's this.

catatan: Saat mengikat konteks untuk jQuery, gunakan jQuery.proxy  [dokumen] sebagai gantinya. Alasan untuk melakukan ini adalah agar Anda tidak perlu menyimpan referensi ke fungsi ketika membatalkan panggilan acara. jQuery menangani itu secara internal.

ECMAScript 6: Gunakan fungsi panah

ECMAScript 6 memperkenalkan fungsi panah, yang dapat dianggap sebagai fungsi lambda. Mereka tidak punya milik sendiri this mengikat. Sebagai gantinya, this dicari dalam lingkup seperti variabel normal. Itu berarti Anda tidak perlu menelepon .bind. Itu bukan satu-satunya perilaku khusus yang mereka miliki, silakan lihat dokumentasi MDN untuk informasi lebih lanjut.

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

Set this panggilan balik - bagian 2

Beberapa fungsi / metode yang menerima callback juga menerima nilai untuk panggilan balik itu this harus mengacu pada. Ini pada dasarnya sama dengan mengikatnya sendiri, tetapi fungsi / metode melakukannya untuk Anda. Array#map  [dokumen] adalah metode semacam itu. Tanda tangannya adalah:

array.map(callback[, thisArg])

Argumen pertama adalah callback dan argumen kedua adalah nilainya this harus mengacu pada. Berikut ini contoh yang dibuat:

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 memberikan nilai atau tidak this biasanya disebutkan dalam dokumentasi fungsi / metode itu. Sebagai contoh, jQuery's $.ajax metode [dokumen] menjelaskan opsi yang disebut context:

Objek ini akan dijadikan konteks dari semua callback terkait Ajax.


Masalah umum: Menggunakan metode objek sebagai callback / penangan kejadian

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

Perhatikan contoh berikut:

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

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

Fungsi itu this.method ditugaskan sebagai pengendali event klik, tetapi jika document.body diklik, nilai yang dicatat akan menjadi undefined, karena di dalam event handler, this mengacu kepada document.body, bukan contoh dari Foo.
Seperti yang sudah disebutkan di awal, apa this mengacu tergantung pada bagaimana fungsinya bernama, bukan bagaimana itu didefinisikan.
Jika kode itu seperti berikut, mungkin lebih jelas bahwa fungsi 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 .bind untuk mengikat secara eksplisit this ke nilai tertentu

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

atau secara eksplisit memanggil fungsi sebagai "metode" objek, dengan menggunakan fungsi anonim sebagai callback / pengendali event 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();

1206
2017-11-29 06:13



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

  1. Kamu dapat memakai mengikat() fungsi.
  2. Menyimpan referensi ke konteks / ini di dalam variabel lain (lihat contoh di bawah).
  3. Gunakan ES6 Panah fungsi.
  4. Ubah kode / desain fungsi / arsitektur - untuk ini Anda harus memiliki perintah 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);
  });
}

139
2017-08-13 10:26



Itu semua dalam sintaks "ajaib" memanggil metode:

object.property();

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

var f = object.property;
f();

Ketika Anda mendapatkan referensi dari suatu metode, itu tidak lagi melekat pada objek, itu hanya referensi ke fungsi polos. Hal yang sama terjadi ketika Anda mendapatkan referensi untuk digunakan sebagai panggilan balik:

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 $.proxy sebagai gantinya, sebagai bind tidak didukung di semua browser:

this.saveNextLevelData($.proxy(this.setAll, this));

35
2018-05-21 00:11



Masalah dengan "konteks"

Istilah "konteks" terkadang digunakan untuk merujuk pada objek yang direferensikan oleh ini. Penggunaannya tidak tepat karena tidak cocok baik secara semantik atau teknis ECMAScript ini.

"Konteks" berarti keadaan yang melingkupi sesuatu yang menambah makna, atau beberapa informasi yang mendahului dan mengikuti yang memberi makna tambahan. Istilah "konteks" digunakan dalam ECMAScript untuk merujuk konteks eksekusi, yang merupakan semua parameter, ruang lingkup dan ini dalam ruang lingkup beberapa kode eksekusi.

Ini ditunjukkan dalam ECMA-262 bagian 10.4.2:

Setel ThisBinding ke nilai yang sama dengan ThisBinding of the   memanggil konteks eksekusi

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

Konteks eksekusi memberikan informasi sekitarnya yang menambah arti kode yang sedang dieksekusi. Ini mencakup lebih banyak informasi yang hanya itu thisBinding.

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


20
2018-06-01 00:44



Pertama, Anda harus memiliki pemahaman yang jelas scope dan perilaku this kata kunci dalam konteks scope.

this & scope :


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

   1) Global Scope

   2) Function Scope

Singkatnya, lingkup global mengacu pada objek jendela. Variabel yang dideklarasikan dalam lingkup global dapat diakses dari mana saja. Di sisi lain, ruang lingkup fungsi berada di dalam fungsi. Variabel yang dideklarasikan di dalam fungsi tidak dapat diakses dari luar dunia secara normal.this kata kunci dalam lingkup global mengacu pada objek jendela.this fungsi dalam juga mengacu pada objek jendela. Jadi this akan selalu merujuk ke jendela sampai kita menemukan cara untuk memanipulasi this untuk menunjukkan konteks pilihan kita sendiri.

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

Berbagai cara untuk memanipulasi this di dalam fungsi callback:

Di sini saya memiliki fungsi konstruktor yang disebut Person. Ini memiliki properti yang disebut name dan empat metode yang disebut sayNameVersion1,sayNameVersion2,sayNameVersion3,sayNameVersion4. Keempatnya memiliki satu tugas spesifik. Menerima panggilan balik dan menjalankannya. Callback memiliki tugas khusus untuk mencatat properti nama dari instance fungsi konstruktor Person.

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)

}

Sekarang mari buat instance dari konstruktor person dan gunakan versi lain dari sayNameVersionX (X mengacu pada 1,2,3,4) metode dengan niceCallback untuk melihat berapa banyak cara kita dapat memanipulasi this di dalam callback untuk merujuk ke person contoh.

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

mengikat: 

Apa yang perlu dilakukan adalah membuat fungsi baru dengan this kata kunci disetel ke nilai yang disediakan.

sayNameVersion1 dan sayNameVersion2 gunakan bind untuk memanipulasi this dari fungsi panggilan balik.

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

yang pertama mengikat this dengan callback di dalam metode itu sendiri. Dan untuk callback yang kedua dilewatkan dengan objek yang terikat padanya.

p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method

p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback

telepon: 

Itu first argument dari call metode digunakan sebagai this di dalam fungsi yang dipanggil callmelekat padanya.

sayNameVersion3 menggunakan call untuk memanipulasi this untuk merujuk ke objek orang yang kita buat, bukan objek jendela.

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

dan itu disebut seperti berikut:

p1.sayNameVersion3(niceCallback)

menerapkan : 

Mirip dengan call, argumen pertama apply mengacu pada objek yang akan ditunjukkan oleh this kata kunci.

sayNameVersion4 menggunakan apply untuk memanipulasi this untuk merujuk ke objek orang

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

dan itu disebut seperti yang berikut. Hanya callback yang dilewatkan,

p1.sayNameVersion4(niceCallback)

15
2017-08-18 17:58



Kita tidak bisa mengikat ini setTimeout(), karena selalu dijalankan dengan objek global (Jendela), jika Anda ingin mengakses this konteks dalam fungsi panggilan balik kemudian dengan menggunakan bind() ke fungsi callback yang bisa kita raih sebagai:

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

9
2017-11-17 14:32