Jak uzyskać dostęp do prawidłowego „this” w wywołaniu zwrotnym?
Mam funkcję konstruktora, która rejestruje program obsługi 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 mogę jednak uzyskać dostępu do data
właściwości utworzonego obiektu wewnątrz wywołania zwrotnego. Wygląda na to, this
że nie odnosi się do obiektu, który został utworzony, ale do innego.
Próbowałem też użyć metody obiektu 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?
Odpowiedzi
O czym powinieneś wiedzieć this
this
(inaczej „kontekst”) jest specjalnym słowem kluczowym wewnątrz każdej funkcji, a jego wartość zależy tylko od sposobu wywołania funkcji, a nie od tego, jak / kiedy / gdzie została zdefiniowana. Zakresy leksykalne nie mają na niego wpływu, tak jak inne zmienne (z wyjątkiem funkcji strzałkowych, 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 this
, zajrzyj do dokumentacji MDN .
Jak odnosić się do prawidłowego this
Użyj funkcji strzałek
ECMAScript 6 wprowadził funkcje strzałkowe , które można traktować jako funkcje lambda. Nie mają własnego this
wiązania. Zamiast tego this
jest wyszukiwany w zakresie, tak jak zwykła zmienna. Oznacza to, że nie musisz dzwonić .bind
. To nie jedyne specjalne zachowanie, jakie mają, zapoznaj się z dokumentacją MDN, aby uzyskać więcej informacji.
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
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 popularne to self
i that
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Ponieważ self
jest to normalna zmienna, jest zgodna z regułami zakresu leksykalnego i jest dostępna wewnątrz wywołania zwrotnego. Ma to również tę zaletę, że można uzyskać dostęp do this
wartości samego wywołania zwrotnego.
Jawny zestaw this
wywołania zwrotnego - część 1
Może się wydawać, że nie masz 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
przypisaną wartością. Funkcja zachowuje się dokładnie tak samo, jak ta, którą wywołałeś, z tą różnicą .bind
, że this
została ustawiona przez Ciebie. Bez względu na to, jak i kiedy ta funkcja zostanie wywołana, this
zawsze będzie odnosić 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łania zwrotne this
z wartością MyConstructor
's this
.
Uwaga: W przypadku kontekstu powiązania dla jQuery użyj zamiast tego jQuery.proxy [docs] . Powodem tego jest to, że nie musisz przechowywać odwołania do funkcji podczas odłączania wywołania zwrotnego zdarzenia. jQuery obsługuje to wewnętrznie.
Zestaw this
oddzwonienia - część 2
Niektóre funkcje / metody, które akceptują wywołania zwrotne, akceptują również wartość, do której this
powinny się one odnosić. Zasadniczo jest to to samo, co samodzielne wiązanie, ale funkcja / metoda robi to za Ciebie. Array#map [docs] jest taką metodą. Jego podpis to:
array.map(callback[, thisArg])
Pierwszy argument to callback, a drugi to wartość, this
do której ma 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: To, czy można przekazać wartość dla, this
jest zwykle wspomniane w dokumentacji tej funkcji / metody. Na przykład metoda jQuery [docs]$.ajax opisuje opcję o nazwie context
:
Ten obiekt stanie się kontekstem wszystkich wywołań zwrotnych związanych z Ajaxem.
Typowy problem: używanie metod obiektów jako wywołań zwrotnych / programów obsługi zdarzeń
Innym typowym przejawem tego problemu jest użycie metody obiektu jako procedury obsługi wywołań zwrotnych / zdarzeń. Funkcje są obywatelami pierwszej klasy w JavaScript, a termin „metoda” to potoczne określenie funkcji, która jest wartością właściwości obiektu. Ale ta funkcja nie ma określonego łącza do swojego obiektu „zawierającego”.
Rozważmy 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 moduł obsługi zdarzenia kliknięcia, ale po document.body
kliknięciu zostanie zarejestrowana wartość undefined
, ponieważ wewnątrz procedury obsługi zdarzenia this
odwołuje się do document.body
instancji, a nie do instancji Foo
.
Jak już wspomniano na początku, to, do czego się this
odnosi, zależy od tego, jak wywoływana jest funkcja , a nie jak jest zdefiniowana .
Jeśli kod byłby podobny do poniższego, może być bardziej oczywiste, że funkcja nie ma niejawnego odwołania 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 wspomniano 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 obsługi wywołania zwrotnego / zdarzenia 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();
Oto kilka sposobów uzyskania dostępu do kontekstu nadrzędnego w kontekście podrzędnym -
- Możesz użyć
bind()
funkcji. - Przechowuj odniesienie do kontekstu / this wewnątrz innej zmiennej (patrz poniższy przykład).
- Użyj funkcji strzałek ES6 .
- Zmień kod / projekt funkcji / architekturę - w tym celu powinieneś mieć kontrolę nad wzorcami projektowymi 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 / this wewnątrz innej zmiennej
function MyConstructor(data, transport) {
var self = this;
this.data = data;
transport.on('data', function() {
alert(self.data);
});
}
3 Funkcja strzałek
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
Wszystko znajduje się w „magicznej” składni wywoływania metody:
object.property();
Kiedy otrzymasz 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 zamiast tego zakres globalny (okno):
var f = object.property;
f();
Kiedy otrzymujesz referencję do metody, nie jest ona już dołączona do obiektu, jest to po prostu odwołanie do zwykłej funkcji. To samo dzieje się, gdy otrzymasz odniesienie do użycia jako wywołanie zwrotne:
this.saveNextLevelData(this.setAll);
W tym miejscu możesz powiązać kontekst z funkcją:
this.saveNextLevelData(this.setAll.bind(this));
Jeśli używasz jQuery, powinieneś $.proxy
zamiast tego użyć metody, ponieważ bind
nie jest obsługiwana we wszystkich przeglądarkach:
this.saveNextLevelData($.proxy(this.setAll, this));
Problem z „kontekstem”
Termin „kontekst” jest czasami używany w odniesieniu do obiektu, do którego odwołuje się this . Jego użycie jest niewłaściwe, ponieważ nie pasuje ani semantycznie, ani technicznie do tego w ECMAScript .
„Kontekst” oznacza okoliczności związane z czymś, co dodaje znaczenia, lub poprzedzające i następujące po nim informacje, które nadają dodatkowe znaczenie. Termin „kontekst” jest używany w skrypcie ECMAScript w odniesieniu do kontekstu wykonania , czyli wszystkich parametrów, zakresu i to w zakresie jakiegoś kodu wykonawczego.
Jest to pokazane w ECMA-262 sekcja 10.4.2 :
Ustaw ThisBinding na taką samą wartość jak ThisBinding kontekstu wykonania wywołującego
który wyraźnie wskazuje, że to jest częścią kontekstu wykonania.
Kontekst wykonania dostarcza otaczających informacji, które dodają znaczenia do wykonywanego kodu. Zawiera znacznie więcej informacji niż tylko thisBinding .
Zatem wartością tego nie jest „kontekst”, to tylko część kontekstu wykonania. Zasadniczo jest to zmienna lokalna, którą można ustawić przez wywołanie dowolnego obiektu w trybie ścisłym na dowolną wartość.
Powinieneś wiedzieć o „tym” słowie kluczowym.
Zgodnie z moim zdaniem można to zaimplementować na trzy sposoby (funkcja Self / Arrow / Bind Method)
Słowo kluczowe tej funkcji zachowuje się nieco inaczej w JavaScript w porównaniu z innymi językami.
Ma również pewne różnice między trybem ścisłym a trybem nieścisłym.
W większości przypadków wartość tego jest określana przez sposób wywołania funkcji.
Nie można go ustawić przez przypisanie podczas wykonywania i może być inny przy każdym wywołaniu funkcji.
ES5 wprowadził metodę bind () do ustawiania wartości funkcji this niezależnie od tego, jak jest wywoływana,
a ES2015 wprowadził funkcje strzałkowe, które nie zapewniają własnego powiązania (zachowuje tę wartość otaczającego kontekstu leksykalnego).
Metoda 1: Jaźń - Jaźń jest używana do utrzymywania odniesienia do oryginału, nawet gdy zmienia się kontekst. Jest to technika często używana w programach obsługi zdarzeń (zwłaszcza w domknięciach).
Odniesienie :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);
});
}
Metoda 2 : Funkcja strzałkowa - wyrażenie funkcji strzałkowej to kompaktowa pod względem składniowym alternatywa dla wyrażenia funkcji regularnej,
chociaż bez własnych powiązań ze słowami kluczowymi this, arguments, super lub new.target.
Wyrażenia funkcji strzałek nie nadają się jako metody i nie można ich używać jako konstruktorów.
Odniesienie :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);
});
}
Metoda 3 : Bind - metoda bind () tworzy nową funkcję, która:
po wywołaniu ma to słowo kluczowe ustawione na podaną wartość,
z daną sekwencją argumentów poprzedzającą jakąkolwiek podaną podczas wywołania nowej funkcji.
Odniesienie: 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);
Po pierwsze, musisz dobrze zrozumieć scope
i zachowywać się 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 okna. Zmienne zadeklarowane w zasięgu globalnym są dostępne z dowolnego miejsca. Z drugiej strony zasięg funkcji znajduje się wewnątrz funkcji. do zmiennej zadeklarowanej wewnątrz funkcji nie można normalnie uzyskać dostępu ze świata zewnętrznego. this
słowo kluczowe w zakresie globalnym odnosi się do obiektu okna. this
Funkcja inside odnosi się również do obiektu window this
, więc zawsze będzie odnosić się do okna, dopóki nie znajdziemy sposobu na manipulację w this
celu wskazania kontekstu, który sami wybraliśmy .
--------------------------------------------------------------------------------
- -
- 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ętrznymi funkcjami wywołania zwrotnego:
Tutaj mam funkcję konstruktora o nazwie Person. Posiada właściwość o nazwie name
i cztery metody zwanej sayNameVersion1
, sayNameVersion2
, sayNameVersion3
, sayNameVersion4
. Wszystkie cztery mają jedno określone zadanie. Akceptuje wywołanie zwrotne i wywołuje je. Callback ma określone zadanie, które polega na zarejestrowaniu 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 konstruktora person i wywołajmy różne wersje metody sayNameVersionX
(X odwołuje się do 1, 2, 3, 4), niceCallback
aby zobaczyć, na ile sposobów możemy manipulować this
wewnętrznym wywołaniem zwrotnym, aby odwołać się do person
instancji.
var p1 = new Person('zami') // create an instance of Person constructor
wiązać:
To, co robi bind, to utworzenie nowej funkcji ze this
słowem kluczowym ustawionym na podaną wartość.
sayNameVersion1
i sayNameVersion2
użyj bind do manipulowania this
funkcją wywołania zwrotnego.
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
pierwsza wiąże się this
z wywołaniem zwrotnym w samej metodzie, a za drugą jest przekazywana z powiązanym 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
połączenie :
first argument
Od call
sposobu stosuje się this
wewnątrz funkcji, które są wywoływane z call
nim związane.
sayNameVersion3
używa call
do manipulowania, this
aby odwołać się do obiektu osoby, który utworzyliśmy, zamiast do obiektu okna.
this.sayNameVersion3 = function(callback){
callback.call(this)
}
i nazywa się to następująco:
p1.sayNameVersion3(niceCallback)
zastosować :
Podobnie jak call
pierwszy argument apply
odnosi się do obiektu, który będzie wskazywany przez this
słowo kluczowe.
sayNameVersion4
używa apply
do manipulacji, this
aby odnieść się do obiektu osoby
this.sayNameVersion4 = function(callback){
callback.apply(this)
}
i nazywa się to następująco. po prostu przekazujemy wywołanie zwrotne,
p1.sayNameVersion4(niceCallback)
Nie możemy tego powiązać setTimeout()
, ponieważ zawsze jest to wykonywane z obiektem globalnym (Window) , jeśli chcesz uzyskać dostęp do this
kontekstu w funkcji wywołania zwrotnego, to używając funkcji bind()
do wywołania zwrotnego możemy osiągnąć jako:
setTimeout(function(){
this.methodName();
}.bind(this), 2000);
Pytanie dotyczy tego, jak this
słowo kluczowe zachowuje się w javascript. this
zachowuje się inaczej niż poniżej,
- Wartość
this
jest zwykle określana przez kontekst wykonywania funkcji. - W zakresie globalnym
this
odnosi się do obiektu globalnego (window
obiektu). - Jeśli tryb ścisły jest włączony dla dowolnej funkcji, wówczas wartość
this
będzie taka,undefined
jak w trybie ścisłym, do obiektu globalnego odwołuje sięundefined
zamiastwindow
obiektu. - Obiekt, który stoi przed kropką, jest tym, do którego będzie przypisane to słowo kluczowe.
- Możemy ustawić wartość to wyraźnie z
call()
,bind()
iapply()
- Gdy
new
używane jest słowo kluczowe (konstruktor), jest ono powiązane z nowo tworzonym obiektem. - Funkcje strzałkowe nie wiążą się
this
- zamiast tegothis
są powiązane leksykalnie (tj. W oparciu o oryginalny kontekst)
Jak sugeruje większość odpowiedzi, możemy użyć funkcji Arrow lub bind()
Method lub Self var. Chciałbym zacytować punkt dotyczący lambd (funkcji strzałki) z Przewodnika po stylach Google JavaScript
Wolę używać funkcji strzałkowych zamiast f.bind (this), a zwłaszcza goog.bind (f, this). Unikaj pisania const self = this. Funkcje strzałkowe są szczególnie przydatne w przypadku wywołań zwrotnych, które czasami przekazują nieoczekiwanie dodatkowe argumenty.
Google wyraźnie zaleca używanie lambd zamiast wiązania lub const self = this
Więc najlepszym rozwiązaniem byłoby użycie lambd jak poniżej,
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
Bibliografia:
- https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
- funkcje-strzałek-vs-bind
Obecnie możliwe jest inne podejście, jeśli w kodzie używane są klasy.
Przy wsparciu pól klas można zrobić to następująco:
class someView {
onSomeInputKeyUp = (event) => {
console.log(this); // this refers to correct value
// ....
someInitMethod() {
//...
someInput.addEventListener('input', this.onSomeInputKeyUp)
Na pewno pod maską jest to stara dobra funkcja strzałki, która wiąże kontekst, ale w tej formie wygląda o wiele wyraźniej niż jawne powiązanie.
Ponieważ jest to propozycja Stage 3, będziesz potrzebować babel i odpowiedniej wtyczki babel, aby ją przetworzyć na razie (08/2018).
Innym podejściem, które jest standardowym sposobem od czasu DOM2 do łączenia się this
w nasłuchiwaniu zdarzeń, które pozwala zawsze usunąć nasłuchiwanie (wśród innych korzyści), jest handleEvent(evt)
metoda z EventListener
interfejsu:
var obj = {
handleEvent(e) {
// always true
console.log(this === obj);
}
};
document.body.addEventListener('click', obj);
Szczegółowe informacje o korzystaniu handleEvent
można znaleźć tutaj:https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38
this
w JS:
Wartość this
w JS jest w 100% określana przez sposób wywołania funkcji, a nie sposób jej zdefiniowania. Możemy stosunkowo łatwo znaleźć wartość this
za pomocą „reguły po lewej stronie kropki” :
- Gdy funkcja jest tworzona przy użyciu słowa kluczowego function, wartością
this
jest obiekt znajdujący się na lewo od kropki wywoływanej funkcji - Jeśli po kropce nie ma żadnego obiektu, wówczas wartość
this
wewnątrz funkcji jest często obiektem globalnym (global
w węźle,window
w przeglądarce). Nie polecałbym używaniathis
tutaj słowa kluczowego, ponieważ jest mniej wyraźne niż użycie czegoś takiegowindow
! - Istnieją pewne konstrukcje, takie jak funkcje strzałkowe i funkcje utworzone za
Function.prototype.bind()
pomocą funkcji a, która może ustalić wartośćthis
. Są to wyjątki od reguły, ale są naprawdę pomocne przy ustalaniu wartościthis
.
Przykład w 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);
Wynik:
Pozwól, że przeprowadzę cię przez wyjścia 1 na 1 (ignorując pierwszy dziennik, zaczynając od drugiego):
this
jest zobj2
powodu lewej reguły kropki, możemy zobaczyć, jaktest1
nazywa sięobj2.test1();
.obj2
jest na lewo od kropki, a tym samymthis
wartości.- Mimo że
obj2
pozostaje z kropki,test2
jest do tego zobowiązanyobj1
za pomocąbind()
metody. Więcthis
wartość jestobj1
. obj2
pozostało z kropką z funkcji, które nazywa się:obj2.test3()
. Dlategoobj2
będzie wartośćthis
.- W tym przypadku:
obj2.test4()
obj2
jest na lewo od kropki. Jednak funkcje strzałkowe nie mają własnegothis
powiązania. Dlatego będzie wiązać się zthis
wartością zakresu zewnętrznego, który jestmodule.exports
obiektem, który został zarejestrowany na początku. - Możemy również określić wartość
this
za pomocącall
funkcji. Tutaj możemy przekazać żądanąthis
wartość jako argument, czyliobj2
w tym przypadku.
Byłem w obliczu problemu z Ngx
linii wykresu xAxisTickFormatting
funkcji, która została wywołana z HTML tak: [xAxisTickFormatting]="xFormat"
. Nie mogłem uzyskać dostępu do zmiennej mojego składnika z zadeklarowanej funkcji. To rozwiązanie pomogło mi rozwiązać problem i znaleźć właściwą. Mam nadzieję, że to pomoże Ngx
wykresowi liniowemu, użytkownicy.
zamiast używać takiej funkcji:
xFormat (value): string {
return value.toString() + this.oneComponentVariable; //gives wrong result
}
Użyj tego:
xFormat = (value) => {
// console.log(this);
// now you have access to your component variables
return value + this.oneComponentVariable
}