Was ist das hier? Innere Operation von JavaScript-Objekten


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

Source: https://habr.com/ru/post/de452192/


All Articles