Jak uzyskać dostęp do prawidłowego "tego" wewnątrz callback?

Mam funkcję konstruktora, która rejestruje obsługę zdarzeń:

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

Nie jestem jednak w stanie uzyskać dostępu do właściwości data utworzonego obiektu wewnątrz wywołania zwrotnego. Wygląda na to, że this nie odnosi się do obiektu, który został utworzony, ale do innego.

Próbowałem też użyć metody obiektowej zamiast funkcji anonimowej:

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

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

Ale wykazuje te same problemy.

Jak mogę uzyskać dostęp do właściwego obiektu?

Author: T.J. Crowder, 2013-11-29

8 answers

Co powinieneś wiedzieć o this

this ( "kontekst") jest specjalnym słowem kluczowym wewnątrz każdej funkcji, a jego wartość zależy tylko od jak funkcja została wywołana, a nie jak/kiedy/gdzie została zdefiniowana. Nie ma to wpływu na zakresy leksykalne, jak inne zmienne (z wyjątkiem funkcji strzałek, patrz poniżej). Oto kilka przykładów:

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`

Aby dowiedzieć się więcej o this, zajrzyj do dokumentacji MDN.


Jak odnosić się do poprawne this

Nie używaj this

W rzeczywistości nie chcesz uzyskać dostępu this w szczególności, ale obiekt, do którego się odnosi . Dlatego prostym rozwiązaniem jest po prostu utworzenie nowej zmiennej, która również odnosi się do tego obiektu. Zmienna może mieć dowolną nazwę, ale typowe to self i that.

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

Ponieważ self jest zmienną normalną, podlega ona regułom zakresu leksykalnego i jest dostępna wewnątrz wywołania zwrotnego. Ma to również tę zaletę, że możesz uzyskać dostęp do this wartość samego wywołania zwrotnego.

Jawnie ustawić this wywołania zwrotnego-Część 1

Może to wyglądać tak, jakbyś nie miał kontroli nad wartością this, ponieważ jej wartość jest ustawiana automatycznie, ale w rzeczywistości tak nie jest.

Każda funkcja ma metodę .bind [docs], która zwraca nową funkcję z this związaną z wartością. Funkcja ma dokładnie takie samo zachowanie jak ta, którą wywołałeś .bind NA, tylko że this został ustawiony przez Ciebie. Bez względu na to, jak i kiedy ta funkcja zostanie wywołana, this zawsze będzie odwoływać się do przekazanej wartości.

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

W tym przypadku wiążemy wywołanie zwrotne this z wartością MyConstructor's this.

Notatka: podczas wiązania kontekstu dla jQuery, użyj jQuery.proxy [docs] zamiast tego. Powodem tego jest to, że nie trzeba przechowywać odniesienia do funkcji podczas rozłączania wywołania zwrotnego zdarzenia. jQuery obsługuje to wewnętrznie.

ECMAScript 6: Użyj funkcji strzałek

ECMAScript 6 wprowadza funkcje strzałek , które można traktować jako funkcje lambda. Nie mają własnego this wiązania. Zamiast tego {[11] } jest sprawdzany w zakresie tak jak normalna zmienna. To znaczy, że nie musisz dzwonić .bind. Więcej informacji można znaleźć w dokumentacji MDN.

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

Zestaw this części zwrotnej 2

Niektóre funkcje / metody akceptujące wywołania zwrotne również akceptują wartość, do której powinno się odnosić wywołanie zwrotne this. Jest to w zasadzie to samo, co wiązanie go samodzielnie, ale funkcja / metoda robi to za Ciebie. Array#map [docs] to taka metoda. Jego podpis to:

array.map(callback[, thisArg])

Pierwszy argument jest wywołaniem zwrotnym, a drugi argument jest wartością this, do której powinna się odnosić. Oto wymyślony przykład:

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

Uwaga: Czy lub nie możesz przekazać wartość this jest zwykle wspomniana w dokumentacji tej funkcji / metody. Na przykład metoda jQuery $.ajax [docs] opisuje opcję o nazwie context:

Ten obiekt stanie się kontekstem wszystkich wywołań zwrotnych związanych z Ajaxem.


Powszechny problem: używanie metod obiektowych jako wywołań zwrotnych / procedur obsługi zdarzeń

Innym powszechnym przejawem tego problemu jest użycie metody obiektowej jako callback/obsługa zdarzeń. Funkcje są pierwszorzędnymi obywatelami w JavaScript, a termin "metoda" jest tylko potocznym terminem dla funkcji, która jest wartością właściwości obiektu. Ale ta funkcja nie ma określonego łącza do obiektu "zawierającego".

Rozważ następujący przykład:

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

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

Funkcja this.method jest przypisana jako Obsługa zdarzenia click, ale jeśli document.body jest kliknięta, rejestrowana wartość będzie undefined, ponieważ wewnątrz obsługi zdarzenia, this odnosi się do document.body, nie instancja Foo.
Jak już wspomniano na początku, to, do czego odnosi się this, zależy od tego, jak funkcja jest nazywana, a nie jak jest zdefiniowana.
Jeśli kod był następujący, może być bardziej oczywiste, że funkcja nie ma ukrytego odniesienia do obiektu:

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


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

Foo.prototype.method = method;

Rozwiązanie jest takie samo jak wspomniane powyżej: jeśli jest dostępne, użyj .bind, aby jawnie powiązać this z określoną wartością

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

Lub jawnie wywołaj funkcję jako "metodę" obiektu, używając funkcji anonimowej jako funkcji callback / event handler i przypisz obiekt (this) do innej zmiennej:

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

Lub użyj funkcji strzałki:

document.body.onclick = () => this.method();
 1302
Author: Felix Kling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-16 04:03:23

Oto kilka sposobów dostępu do kontekstu rodzica wewnątrz kontekstu dziecka -

  1. możesz użyć funkcji bind().
  2. przechowuje odniesienie do kontekstu / tego wewnątrz innej zmiennej(patrz poniższy przykład).
  3. Użyj funkcji ES6 Strzałka.
  4. Alter code / function design / architecture - w tym celu powinieneś posiadać polecenie nad design patterns w javascript.

1. Użyj bind() funkcji

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

Jeśli używasz underscore.js - http://underscorejs.org/#bind

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

2 Przechowuj odniesienie do kontekstu/to wewnątrz innej zmiennej

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

3 Funkcja Strzałki

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}
 151
Author: Mohan Dere,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-20 09:43:07

To wszystko w "magicznej" składni wywołania metody:

object.property();

Gdy uzyskasz właściwość z obiektu i wywołasz ją za jednym razem, obiekt będzie kontekstem dla metody. Jeśli wywołasz tę samą metodę, ale w oddzielnych krokach, kontekstem jest globalny zakres (okno):

var f = object.property;
f();

Kiedy uzyskasz odniesienie do metody, nie jest ona już dołączona do obiektu, jest to tylko odniesienie do zwykłej funkcji. To samo dzieje się, gdy otrzymasz odniesienie do wykorzystania jako callback:

this.saveNextLevelData(this.setAll);

Tutaj można powiązać kontekst z funkcją:

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

Jeśli używasz jQuery, powinieneś zamiast tego użyć metody $.proxy, ponieważ {[6] } nie jest obsługiwana we wszystkich przeglądarkach:

this.saveNextLevelData($.proxy(this.setAll, this));
 37
Author: Guffa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-21 00:11:50

Problem z "kontekstem"

Termin "kontekst" jest czasami używany w odniesieniu do obiektu, do którego odnosi się this . Jego użycie jest niewłaściwe, ponieważ nie pasuje ani semantycznie, ani technicznie do ECMAScript to.

"Context" oznacza okoliczności otaczające coś, co dodaje znaczenie, lub jakieś poprzedzające i następujące informacje, które nadają dodatkowe znaczenie. Termin "kontekst" jest używany w ECMAScript w odniesieniu do kontekst wykonania, czyli wszystkie parametry, zakres i to w zakresie jakiegoś kodu wykonującego.

Jest to pokazane w sekcji ECMA-262 10.4.2:

Ustaw wartość ThisBinding na tę samą wartość co wywołanie kontekstu wykonania

Co wyraźnie wskazuje, że to jest częścią kontekstu wykonania.

Kontekst wykonania dostarcza otaczających informacji, które dodają znaczenia kod, który jest wykonywany. Zawiera znacznie więcej informacji, że tylko thisBinding.

Więc wartość to nie jest "kontekstem", to tylko część kontekstu wykonania. Jest to w zasadzie zmienna lokalna, która może być ustawiona przez wywołanie dowolnego obiektu i w trybie ścisłym, na dowolną wartość.

 21
Author: RobG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-14 14:23:37

Po pierwsze, musisz mieć jasne zrozumienie scope i zachowanie this słowo kluczowe w kontekście scope.

this & scope :


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

   1) Global Scope

   2) Function Scope

W skrócie, zasięg globalny odnosi się do obiektu window.Zmienne zadeklarowane w zasięgu globalnym są dostępne z dowolnego miejsca.Z drugiej strony zakres funkcji znajduje się wewnątrz funkcji.zmienna zadeklarowana wewnątrz funkcji nie może być dostępna ze świata zewnętrznego normalnie.this słowo kluczowe w zakresie globalnym odnosi się do obiektu window.this funkcja wewnętrzna odnosi się również do okna object.So this będzie zawsze odnosić się do okna, dopóki nie znajdziemy sposobu na manipulowanie this aby wskazać wybrany przez nas kontekst.

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

Różne sposoby manipulowania this wewnątrz funkcji wywołania zwrotnego:

Tutaj mam funkcję konstruktora zwaną osobą. Posiada nieruchomość o nazwie name i cztery metody zwane sayNameVersion1,sayNameVersion2,sayNameVersion3,sayNameVersion4. Wszystkie cztery mają jedno konkretne zadanie.Zaakceptuj oddzwonienie i wywołaj je.Wywołanie zwrotne ma określone zadanie, które polega na zalogowaniu właściwości name instancji funkcji konstruktora 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)

}

Teraz stwórzmy instancję z person constructor i wywołajmy różne wersje sayNameVersionX ( x odnosi się do metody 1,2,3,4) z niceCallback aby zobaczyć jak na wiele sposobów możemy manipulować this wewnątrz callback, aby odnieść się do person przykład.

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

Bind :

Co robi bind, to tworzy nową funkcję z this słowo kluczowe ustawione na podaną wartość.

sayNameVersion1 i sayNameVersion2 użyj bind do manipulowania this funkcji wywołania zwrotnego.

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

First one bind this z wywołaniem zwrotnym wewnątrz samej metody.A dla drugiego wywołanie zwrotne jest przekazywane z przypisanym do niego obiektem.

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 Z call metoda jest stosowana jako this wewnątrz funkcji, która jest wywoływana z call przywiązany do niego.

sayNameVersion3 zastosowania call aby manipulować this aby odnieść się do utworzonego przez nas obiektu person zamiast do obiektu window.

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

I nazywa się tak: :

p1.sayNameVersion3(niceCallback)

Zastosuj :

Podobne do call, pierwszy argument apply odnosi się do obiektu, który zostanie wskazany przez this słowo kluczowe.

sayNameVersion4 zastosowania apply manipulować this aby odnieść się do obiektu person

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

I nazywa się tak jak poniżej.Po prostu połączenie zwrotne jest przekazywane,

p1.sayNameVersion4(niceCallback)
 16
Author: AL-zami,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-19 21:01:54

Nie możemy powiązać tego z setTimeout(), ponieważ zawsze wykonuje się to z global object (Window) , Jeśli chcesz uzyskać dostęp do kontekstu this w funkcji wywołania zwrotnego, to używając bind() do funkcji wywołania zwrotnego możemy osiągnąć jako:

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);
 13
Author: Datta Chanewad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-11 07:47:27

Innym podejściem, które jest standardowym sposobem od DOM2 wiązania this wewnątrz słuchacza zdarzeń, który pozwala zawsze usunąć słuchacz (wśród innych korzyści), jest Metoda handleEvent(evt) z interfejsu EventListener:

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

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

Szczegółowe informacje na temat korzystania z handleEvent można znaleźć tutaj: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38

 2
Author: Andrea Puddu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-28 09:10:52

Obecnie możliwe jest inne podejście, jeśli klasy są używane w kodzie.

Z obsługą pól klasy można zrobić to w następny sposób:

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

Na pewno pod maską to wszystko stara dobra funkcja arrow, która wiąże kontekst, ale w tej formie wygląda znacznie wyraźniej niż wyraźne Wiązanie.

Ponieważ jest to propozycja etapu 3, Będziesz potrzebował babel i odpowiedniej wtyczki babel , aby przetworzyć ją jak na razie(08/2018).

 0
Author: skyboyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-22 13:38:59