Ustalenie wartości pola this
w języku JavaScript może być trudne. Zobacz, jak to zrobić...
Język this
JavaScriptu jest początkiem wielu żartów. To dlatego, że jest on dość skomplikowany.
Deweloperzy wykonują jednak bardziej skomplikowane czynności związane z określoną domeną, aby uniknąć problemów z this
. Jeśli nie masz pewności, co to jest this
, mamy nadzieję, że te informacje okażą się przydatne. To jest mój przewodnik po this
.
Zacznę od najbardziej konkretnej sytuacji i zakończę od najmniej konkretnej sytuacji. Ten artykuł jest jak obszerny if (…) … else if () … else if (…) …
, więc możesz od razu przejść do pierwszej sekcji pasującej do tego, który oglądasz.
- Jeśli funkcja jest zdefiniowana jako funkcja strzałki
- W przeciwnym razie, jeśli funkcja/klasa jest wywoływana za pomocą metody
new
- W przeciwnym razie, jeśli funkcja ma „powiązaną” wartość
this
- W przeciwnym razie, jeśli na czas połączenia ustawiono
this
- W przeciwnym razie, jeśli funkcja zostanie wywołana przez obiekt nadrzędny (
parent.func()
) - W przeciwnym razie, jeśli funkcja lub zakres nadrzędny są w trybie ścisłym
- W innym przypadku
Jeśli funkcja jest zdefiniowana jako funkcja strzałki:
const arrowFunction = () => {
console.log(this);
};
W tym przypadku wartość this
jest zawsze taka sama jak wartość this
w zakresie nadrzędnym:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Funkcje strzałek są świetne, ponieważ nie można zmienić wewnętrznej wartości this
. Jest ona zawsze taka sama jak zewnętrzna wartość this
.
Inne przykłady
W przypadku funkcji strzałek wartości this
nie można zmienić za pomocą funkcji bind
:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
W przypadku funkcji strzałek wartości this
nie można zmienić za pomocą poleceń call
ani apply
:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
W przypadku funkcji strzałek wartości this
nie można zmienić przez wywołanie tej funkcji jako elementu innego obiektu:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
W przypadku funkcji strzałek wartości this
nie można zmienić przez wywołanie tej funkcji jako konstruktora:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
Metody instancji „Bound”
Jeśli w przypadku metod instancji chcesz mieć pewność, że this
zawsze odwołuje się do instancji klasy, najlepszym sposobem jest użycie funkcji strzałek i pól klas:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
Ten wzorzec jest bardzo przydatny w przypadku korzystania w komponentach z metod instancji, takich jak odbiorniki zdarzeń (np. komponenty reakcji lub komponenty sieciowe).
Powyżej może wydawać się, że narusza to regułę „this
będzie taka sama jak this
w zakresie nadrzędnym”, ale jeśli spojrzysz na pola klas jako cukier składniowy do ustawiania rzeczy w konstruktorze:
class Whatever {
someMethod = (() => {
const outerThis = this;
return () => {
// Always logs `true`:
console.log(this === outerThis);
};
})();
}
// …is roughly equivalent to:
class Whatever {
constructor() {
const outerThis = this;
this.someMethod = () => {
// Always logs `true`:
console.log(this === outerThis);
};
}
}
Alternatywne pattry obejmują powiązanie istniejącej funkcji w konstruktorze lub przypisanie funkcji w konstruktorze. Jeśli z jakiegoś powodu nie możesz używać pól klas, rozsądną alternatywą jest przypisanie funkcji w konstruktorze:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
W przeciwnym razie, jeśli funkcja lub klasa jest wywoływana za pomocą metody new
:
new Whatever();
Powyższe wywołanie wywoła Whatever
(lub jego funkcję konstruktora, jeśli jest to klasa) z this
ustawionym jako wynik Object.create(Whatever.prototype)
.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
To samo dotyczy starszych konstruktorów:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Inne przykłady
Po wywołaniu za pomocą funkcji new
wartości this
nie można zmienić za pomocą funkcji bind
:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
Po wywołaniu funkcji new
wartości this
nie można zmienić przez wywołanie tej funkcji jako członka innego obiektu:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
W przeciwnym razie, jeśli funkcja ma „powiązaną” wartość this
:
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Przy każdym wywołaniu funkcji boundFunction
jej wartość this
jest obiektem przekazywanym do funkcji bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Inne przykłady
Podczas wywoływania funkcji granicznej wartości this
nie można zmienić za pomocą call
ani apply
:
// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
Podczas wywoływania funkcji granicznej nie można zmienić wartości this
przez wywołanie tej funkcji jako członka innego obiektu:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
W innym przypadku, jeśli na czas połączenia ustawiono this
:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
Wartość this
to obiekt przekazywany do call
/apply
.
Element this
ma inną wartość, np. przez detektory zdarzeń DOM, przez co korzystanie z niego może sprawić, że kod będzie trudny do zrozumienia:
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); });
W przypadkach takich jak powyżej nie używam this
, a zamiast tego:
element.addEventListener('click', (event) => { // Ideally, grab it from a parent scope: console.log(element); // But if you can't do that, get it from the event object: console.log(event.currentTarget); });
W przeciwnym razie, jeśli funkcja zostanie wywołana przez obiekt nadrzędny (parent.func()
):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
W tym przypadku funkcja jest wywoływana jako element składowy obj
, więc this
ma wartość obj
. Dzieje się to w czasie wywołania, więc link jest uszkodzony, jeśli funkcja zostanie wywołana bez obiektu nadrzędnego lub innego obiektu nadrzędnego:
const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);
const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);
someMethod() === obj
ma wartość fałsz, ponieważ someMethod
nie jest wywoływany jako członek grupy obj
. Być może udało Ci się natrafić na ten błąd podczas próby wykonania polecenia podobnego do tego:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Awaria, ponieważ implementacja querySelector
analizuje własną wartość this
i oczekuje, że będzie rodzajowym węzłem DOM, co powoduje zerwanie tego połączenia. Aby poprawnie wykonać powyższe czynności:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Ciekawostka: nie wszystkie interfejsy API używają this
wewnętrznie. Metody konsoli takie jak console.log
zostały zmienione, aby uniknąć odwołań this
, więc log
nie musi być powiązany z console
.
W przeciwnym razie, jeśli funkcja lub zakres nadrzędny są w trybie ścisłym:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
W tym przypadku wartość this
jest nieokreślona. 'use strict'
nie jest potrzebny, jeśli zakres nadrzędny jest w trybie ścisłym (a wszystkie moduły są w trybie ścisłym).
W innym przypadku:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
W tym przypadku wartość this
jest taka sama jak wartość globalThis
.
Uff...
Gotowe! To wszystko, co wiem o firmie this
. Masz pytania? Coś mi umknęło? Możesz napisać do mnie tweeta.
Dziękujemy Mathias Bynens, Ingvar Stepanyan i Thomas Steiner za recenzję.