Es kann schwierig sein, den Wert von this
in JavaScript zu ermitteln. Hier ist eine Anleitung:
this
von JavaScript ist das Kernstück vieler Witze, und das liegt daran, dass es ziemlich kompliziert ist.
Ich habe jedoch schon Entwickler gesehen, die viel kompliziertere und domänenspezifische Dinge getan haben, um diese this
zu vermeiden. Wenn du dir nicht sicher bist, was this
bedeutet, hoffen wir, dass dir diese Informationen weiterhelfen. Dies ist mein this
-Leitfaden.
Ich beginne mit der spezifischsten Situation und endet mit der am wenigsten spezifischen. Dieser Artikel ist ein bisschen wie ein großes if (…) … else if () … else if (…) …
. Sie können also direkt zum ersten Abschnitt springen, der dem Code entspricht, den Sie sich ansehen.
- Wenn die Funktion als Pfeilfunktion definiert ist
- Andernfalls, wenn die Funktion/Klasse mit
new
aufgerufen wird - Andernfalls, wenn die Funktion einen gebundenen
this
-Wert hat - Andernfalls, wenn
this
bei einem Anruf festgelegt wurde - Andernfalls, wenn die Funktion über ein übergeordnetes Objekt (
parent.func()
) aufgerufen wird - Andernfalls, wenn die Funktion oder der übergeordnete Bereich sich im strengen Modus befindet
- Andernfalls
Wenn die Funktion als Pfeilfunktion definiert ist:
const arrowFunction = () => {
console.log(this);
};
In diesem Fall ist der Wert von this
immer mit this
im übergeordneten Bereich identisch:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Pfeilfunktionen sind sehr nützlich, da der innere Wert von this
nicht geändert werden kann. Er ist immer gleich wie der äußere this
.
Weitere Beispiele
Bei Pfeilfunktionen kann der Wert von this
nicht mit bind
geändert werden:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
Bei Pfeilfunktionen kann der Wert von this
nicht mit call
oder apply
geändert werden:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
Bei Pfeilfunktionen kann der Wert von this
nicht geändert werden, indem die Funktion als Mitglied eines anderen Objekts aufgerufen wird:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
Bei Pfeilfunktionen kann der Wert von this
nicht durch Aufrufen der Funktion als Konstruktor geändert werden:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
„Gebundene“ Instanzmethoden
Wenn Sie bei Instanzmethoden dafür sorgen möchten, dass this
immer auf die Klasseninstanz verweist, verwenden Sie am besten Pfeilfunktionen und Klassenfelder:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
Dieses Muster ist sehr nützlich, wenn Instanzmethoden als Ereignislistener in Komponenten verwendet werden (z. B. React- oder Webkomponenten).
Das oben genannte Beispiel scheint die Regel zu verletzen, dass this
mit this
im übergeordneten Gültigkeitsbereich identisch sein muss. Es ergibt aber Sinn, wenn Sie Klassenfelder als syntaktischen Zucker für das Festlegen von Elementen im Konstruktor betrachten:
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);
};
}
}
Alternative Muster beinhalten das Binden einer vorhandenen Funktion im Konstruktor oder die Zuweisung der Funktion im Konstruktor. Wenn Sie aus irgendeinem Grund keine Klassenfelder verwenden können, ist die Zuweisung von Funktionen im Konstruktor eine angemessene Alternative:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
Andernfalls, wenn die Funktion/Klasse mit new
aufgerufen wird:
new Whatever();
Der obige Befehl ruft Whatever
(oder seine Konstruktorfunktion, wenn es sich um eine Klasse handelt) auf, wobei this
auf das Ergebnis von Object.create(Whatever.prototype)
festgelegt ist.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
Dasselbe gilt für Konstruktoren mit älterem Stil:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Weitere Beispiele
Wenn new
aufgerufen wird, kann der Wert von this
nicht mit bind
geändert werden:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
Bei Aufruf mit new
kann der Wert von this
nicht durch Aufrufen der Funktion als Element eines anderen Objekts geändert werden:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
Wenn die Funktion einen „bound“-Wert für this
hat, gilt Folgendes:
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Bei jedem Aufruf von boundFunction
wird der this
-Wert das an bind
übergebene Objekt (boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Weitere Beispiele
Beim Aufrufen einer gebundenen Funktion kann der Wert von this
nicht mit call
oder apply
geändert werden:
// 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);
Beim Aufrufen einer verknüpften Funktion kann der Wert von this
nicht geändert werden, indem die Funktion als Mitglied eines anderen Objekts aufgerufen wird:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
Andernfalls, wenn this
zum Zeitpunkt des Anrufs festgelegt ist:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
Der Wert von this
ist das Objekt, das an call
/apply
übergeben wird.
Leider wird this
durch Dinge wie DOM-Ereignis-Listener auf einen anderen Wert gesetzt. Die Verwendung kann zu schwer verständlichem Code führen:
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); });
Ich vermeide die Verwendung von this
in Fällen wie oben und verwende stattdessen:
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); });
Andernfalls, wenn die Funktion über ein übergeordnetes Objekt (parent.func()
) aufgerufen wird:
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
In diesem Fall wird die Funktion als Mitglied von obj
aufgerufen, sodass this
gleich obj
ist. Das geschieht zum Zeitpunkt des Aufrufs. Die Verknüpfung wird also getrennt, wenn die Funktion ohne übergeordnetes Objekt oder mit einem anderen übergeordneten Objekt aufgerufen wird:
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
ist falsch, weil someMethod
nicht als Mitglied von obj
aufgerufen wird. Möglicherweise haben Sie dieses Problem schon einmal bei einem ähnlichen Versuch festgestellt:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Dies funktioniert nicht, weil bei der Implementierung von querySelector
der eigene this
-Wert geprüft wird und erwartet wird, dass es sich um einen bestimmten DOM-Knoten handelt. Die oben beschriebene Verbindung unterbricht diese Verbindung. So gehen Sie vor:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Fun Fact: Nicht alle APIs verwenden this
intern. Konsolenmethoden wie console.log
wurden geändert, um this
-Verweise zu vermeiden. Daher muss log
nicht an console
gebunden sein.
Wenn sich die Funktion oder der übergeordnete Bereich im strikten Modus befindet, gilt Folgendes:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
In diesem Fall ist der Wert von this
nicht definiert. 'use strict'
ist in der Funktion nicht erforderlich, wenn sich der übergeordnete Bereich im strengen Modus befindet (und alle Module sich im strengen Modus befinden).
Andernfalls:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
In diesem Fall ist der Wert von this
mit globalThis
identisch.
Geschafft!
Webseite. Mehr Informationen über this
. Fragen? Etwas, das ich übersehen habe? Sie können gerne an mich twittern.
Vielen Dank an Mathias Bynens, Ingvar Stepanyan und Thomas Steiner für die Überprüfung.