Ustalenie wartości this
może być trudne w JavaScript. Oto jak to zrobić:
this
jest obiektem wielu żartów, ponieważ jest dość skomplikowana.
Jednak widzimy, że deweloperzy stosują znacznie bardziej skomplikowane rozwiązania, aby uniknąć korzystania z tego this
. Jeśli nie masz pewności co do this
, mam nadzieję, że te informacje okażą się przydatne. To mój przewodnik this
.
Zaczniemy od najbardziej konkretnej sytuacji, a skończymy na najmniej konkretnej. Ten artykuł jest trochę jak duża tabela if (…) … else if () … else if (…) …
, więc możesz przejść bezpośrednio do pierwszej sekcji, która pasuje do kodu, który Cię interesuje.
- Jeśli funkcja jest zdefiniowana jako funkcja strzałki
- W przeciwnym razie, jeśli funkcja lub klasa jest wywoływana z
new
- W przeciwnym razie, jeśli funkcja ma wartość „bound”
this
- W przeciwnym razie, jeśli
this
jest ustawiony na czas rozmowy - W przeciwnym razie, jeśli funkcja jest wywoływana za pomocą obiektu nadrzędnego (
parent.func()
) - W przeciwnym razie, jeśli funkcja lub zakres nadrzędny jest 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 this
w zakresie nadrzędnym:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Funkcje strzałek są świetne, ponieważ wewnętrzna wartość funkcji this
nie może się zmienić. Jest zawsze taka sama jak zewnętrzna wartość this
.
Inne przykłady
W przypadku funkcji strzałki 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łki wartości this
nie można zmienić za pomocą funkcji 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łki wartości this
nie można zmienić przez wywołanie funkcji jako elementu innego obiektu:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
W przypadku funkcji strzałki wartości this
nie można zmienić, wywołując funkcję jako konstruktor:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
Metody instancji „związane”
Jeśli w przypadku metod instancji chcesz mieć pewność, że this
zawsze odnosi się do instancji klasy, najlepiej użyć funkcji strzałki i pól klasy:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
Ten wzór jest bardzo przydatny, gdy używasz metod instancji jako detektorów zdarzeń w komponentach (takich jak komponenty React lub komponenty sieciowe).
Powyższe może wyglądać na naruszenie reguły, że „this
jest takie samo jak this
w zakresie nadrzędnym”, ale zaczyna to nabierać sensu, gdy potraktujesz pola klasy jako element składnikowy do ustawiania wartości 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);
};
}
}
Alternatywnym wzorcem jest wiązanie istniejącej funkcji w konstruktorze lub przypisywanie funkcji w konstruktorze. Jeśli z jakiegoś powodu nie możesz używać pól klasy, przypisanie funkcji w konstruktorze jest rozsądną alternatywą:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
W przeciwnym razie, jeśli funkcja lub klasa jest wywoływana za pomocą new
:
new Whatever();
Powyższy kod wywoła funkcję Whatever
(lub jej konstruktor, jeśli jest to klasa) z wartością this
równą wynikowi funkcji 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
Gdy funkcja jest wywoływana za pomocą new
, wartości this
nie można zmienić za pomocą bind
:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
Gdy funkcja jest wywoływana za pomocą new
, wartości this
nie można zmienić, wywołując funkcję jako element innego obiektu:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
Jeśli jednak funkcja ma wartość „bound” this
:
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Gdy wywoływana jest funkcja boundFunction
, jej parametr this
będzie 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 ograniczonej wartości this
nie można zmienić za pomocą instrukcji 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 powiązanej wartości this
nie można zmienić, wywołując funkcję jako element innego obiektu:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
W przeciwnym razie, jeśli this
jest ustawiony w momencie połączenia:
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 przekazany do call
/apply
.
Niestety this
jest ustawiany na inną wartość przez takie elementy jak odbiorniki zdarzeń DOM, a jego użycie może spowodować powstanie kodu, który jest 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 takich przypadkach unikam stosowania 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);
});
Jeśli funkcja jest wywoływana za pomocą obiektu nadrzędnego (parent.func()
), wykonaj jedną z tych czynności:
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
W tym przypadku funkcja jest wywoływana jako element obiektu obj
, więc this
będzie równe obj
. Dzieje się tak w momencie wywołania, więc połączenie jest zerwane, jeśli funkcja zostanie wywołana bez obiektu nadrzędnego lub z innym obiektem nadrzędnym:
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 element obj
. Ten problem może wystąpić, gdy spróbujesz wykonać jedną z tych czynności:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
To powoduje błąd, ponieważ implementacja querySelector
sprawdza własną wartość this
i oczekuje, że będzie to węzeł DOM. Powyższe działanie powoduje przerwanie tego połączenia. Aby uzyskać prawidłowe wyniki:
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ń do this
, więc log
nie musi być powiązana z console
.
Jeśli jednak zakres funkcji lub zakres nadrzędny jest w trybie ścisłym:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
W tym przypadku wartość this
jest niezdefiniowana. W funkcja nie jest potrzebna zmienna 'use strict'
, jeśli zakres nadrzędny jest w trybie ścisłym (a wszystkie moduły są w tym trybie).
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...
To wszystko. To wszystko, co wiem o this
. Masz pytania? Czy coś mi umknęło? Wyślij mi tweeta.
Dziękujemy Mathiasowi Bynensowi, Ingvarowi Stepanyanowi i Thomasowi Steinerowi za sprawdzenie.