
Foto: Neugierige Liliana Saeb (CC BY 2.0)
JavaScript ist eine Multi-Paradigmen-Sprache, die objektorientierte Programmierung und dynamische Verknüpfung unterstützt. Die dynamische Verknüpfung ist ein leistungsstarkes Konzept, mit dem Sie die Struktur von JavaScript-Code zur Laufzeit ändern können. Diese zusätzliche Leistung und Flexibilität wird jedoch auf Kosten einiger Verwirrung erreicht, die größtenteils mit this
Verhalten in JavaScript zusammenhängen.
Dynamische Verknüpfung
Bei der dynamischen Bindung erfolgt die Definition der aufzurufenden Methode zur Laufzeit und nicht zur Kompilierungszeit. JavaScript macht dies mit this
und der Prototypenkette. Insbesondere innerhalb der Methode wird this
während des Aufrufs bestimmt, und der Wert davon hängt davon ab, wie die Methode definiert wurde.
Lass uns ein Spiel spielen. Ich nenne sie "Was ist this
hier?"
const a = { a: 'a' }; const obj = { getThis: () => this, getThis2 () { return this; } }; obj.getThis3 = obj.getThis.bind(obj); obj.getThis4 = obj.getThis2.bind(obj); const answers = [ obj.getThis(), obj.getThis.call(a), obj.getThis2(), obj.getThis2.call(a), obj.getThis3(), obj.getThis3.call(a), obj.getThis4(), obj.getThis4.call(a) ];
Überlegen Sie, wie console.log()
die Werte im console.log()
answers
und überprüfen Sie Ihre Antworten mit console.log()
. Vermutlich?
Beginnen wir mit dem ersten Fall und fahren der Reihe nach fort. obj.getThis()
gibt undefined
, aber warum? Pfeilfunktionen haben this
niemals. Stattdessen nehmen sie dies immer aus dem lexikalischen Bereich ( ca. Lexikalisch ). Für die Wurzel des ES6-Moduls hat der lexikalische Bereich einen undefined
Wert. obj.getThis.call(a)
aus demselben Grund ebenfalls nicht definiert. Bei .call()
kann dies auch mit .call()
oder .bind()
nicht überschrieben werden. this
wird immer aus dem lexikalischen Bereich übernommen.
obj.getThis2()
die Bindung während des Methodenaufrufs ab. Wenn diese Bindung für diese Funktion zuvor nicht vorhanden war, kann sie daran gebunden werden (da dies keine Pfeilfunktion ist). In diesem Fall ist this
ein obj
Objekt, das zum Zeitpunkt des obj
der Methode gebunden ist .
oder [squareBracket]
. ( implizite Bindung beachten )
obj.getThis2.call(a)
etwas komplizierter. Die Methode call()
ruft eine Funktion mit dem angegebenen Wert und optionalen Argumenten auf. Mit anderen Worten, die Methode .call()
this
Bindung vom Parameter .call()
, sodass obj.getThis2.call(a)
das Objekt a
zurückgibt. ( explizite Bindung beachten )
Im Fall von obj.getThis3 = obj.getThis.bind(obj);
Wir versuchen, eine Pfeilfunktion mit this
Grenze zu erhalten, die, wie wir bereits herausgefunden haben, nicht funktioniert. obj.getThis3()
wir für obj.getThis3()
bzw. obj.getThis3.call(a)
undefined
.
Bei regulären Methoden können Sie binden, sodass obj.getThis4()
wie erwartet obj.getThis4()
zurückgibt. Er hat hier bereits seine Bindung erhalten obj.getThis4 = obj.getThis2.bind(obj);
, und obj.getThis4.call(a)
berücksichtigt die erste Bindung und gibt obj
anstelle von a
.
Verdrehter Ball
Wir werden das gleiche Problem lösen, aber dieses Mal verwenden wir eine class
mit öffentlichen Feldern, um das Objekt zu beschreiben ( Stufe 3-Innovationen zum Zeitpunkt dieses Schreibens sind standardmäßig in Chrome und mit den @babel/plugin-offer-class-properties
verfügbar):
class Obj { getThis = () => this getThis2 () { return this; } } const obj2 = new Obj(); obj2.getThis3 = obj2.getThis.bind(obj2); obj2.getThis4 = obj2.getThis2.bind(obj2); const answers2 = [ obj2.getThis(), obj2.getThis.call(a), obj2.getThis2(), obj2.getThis2.call(a), obj2.getThis3(), obj2.getThis3.call(a), obj2.getThis4(), obj2.getThis4.call(a) ];
Denken Sie über die Antworten nach, bevor Sie fortfahren.
Bist du bereit
Alle Aufrufe außer obj2.getThis2.call(a)
geben eine Instanz des Objekts zurück. obj2.getThis2.call(a)
gibt a
. Pfeilfunktionen erhalten this
immer noch aus ihrer lexikalischen Umgebung. Es gibt einen Unterschied darin, wie this
von der lexikalischen Umgebung für Klasseneigenschaften definiert wird. Im Inneren sieht die Initialisierung von Klasseneigenschaften ungefähr so aus:
class Obj { constructor() { this.getThis = () => this; } ...
Mit anderen Worten, die Pfeilfunktion wird im Konstruktorkontext definiert. Da dies eine Klasse ist, besteht die einzige Möglichkeit, eine Instanz zu erstellen, darin, das new
Schlüsselwort zu verwenden (das Weglassen von new
führt zu einem Fehler). Eines der wichtigsten Dinge, die das new
Schlüsselwort tut, ist, eine neue Instanz des Objekts zu erstellen und this
im Konstruktor this
zu binden. Dieses Verhalten sollte in Kombination mit anderen oben erwähnten Verhaltensweisen den Rest erklären.
Fazit
Warst du erfolgreich? Wenn Sie genau wissen, wie sich this
in JavaScript verhält, sparen Sie viel Zeit beim Debuggen komplexer Probleme. Wenn Sie bei den Antworten einen Fehler gemacht haben, bedeutet dies, dass Sie ein wenig üben müssen. Übe mit Beispielen, gehe dann zurück und teste dich erneut, bis du den Test ausführen und jemand anderem erklären kannst, warum die Methoden das zurückgeben, was sie zurückgeben.
Wenn es schwieriger war als erwartet, sind Sie nicht allein. Ich habe viele Entwickler zu diesem Thema befragt und ich denke, dass bisher nur einer von ihnen diese Aufgabe bewältigt hat.
Was als Suche nach dynamischen Methoden begann, die Sie mit .call()
, .bind()
oder .apply()
umleiten konnten .bind()
durch das Hinzufügen von Klassenmethoden und .apply()
viel komplizierter. Vielleicht sollten Sie sich noch einmal darauf konzentrieren. Denken Sie daran, dass Pfeilfunktionen dies immer aus dem lexikalischen Bereich übernehmen und die class
this
tatsächlich lexikalisch durch den Klassenkonstruktor unter der Haube begrenzt. Wenn Sie dies jemals bezweifeln, denken Sie daran, dass Sie einen Debugger verwenden können, um seinen Wert zu überprüfen.
Denken Sie daran, dass Sie bei der Lösung vieler JavaScript-Aufgaben darauf verzichten können. Nach meiner Erfahrung kann fast alles in Form von reinen Funktionen neu definiert werden , die alle als explizite Parameter verwendeten Argumente verwenden ( this
kann als implizite Variable betrachtet werden). Die durch reine Funktionen beschriebene Logik ist deterministisch, was sie überprüfbarer macht. Außerdem treten bei diesem Ansatz keine Nebenwirkungen auf. Daher ist es im Gegensatz zu den Momenten der Manipulation unwahrscheinlich, dass Sie etwas kaputt machen. Jedes Mal, wenn this
eingestellt wird, kann etwas in Abhängigkeit von seinem Wert kaputt gehen.
Manchmal ist this
jedoch nützlich. Zum Beispiel, um Methoden zwischen einer großen Anzahl von Objekten auszutauschen. Selbst in der funktionalen Programmierung kann this
verwendet werden, um auf andere Objektmethoden zuzugreifen, um die algebraischen Transformationen zu implementieren, die erforderlich sind, um neue Algebren auf vorhandenen aufzubauen. Mit this.map()
und this.constructor.of()
kann also die universelle .flatMap()
erhalten werden.
Vielen Dank für die Hilfe bei der Übersetzung von wksmirnowa und VIBaH_dev