Articles

Eine kurze Einführung in die Metaprogrammierung in JavaScript

Reflexion

Reflexion, im Gegensatz zur „Codegenerierung“, ist ein Prozess zur Änderung der zugrunde liegenden Mechanik der Sprache. Die Reflexion kann zur Kompilierungszeit oder zur Laufzeit erfolgen, aber wir bleiben bei der Laufzeitreflexion, da wir über JavaScript sprechen, sodass keine Reflexion zur Kompilierungszeit möglich ist. Die hier diskutierten Konzepte könnten jedoch auch auf eine kompilierte Sprache anwendbar sein.

Da wir verstanden haben, dass es bei der Reflexion darum geht, die zugrunde liegende Mechanik der Sprache zu verändern, wurde sie in drei Hauptkategorien unterteilt, nämlich. introspektion, Fürbitte und Modifikation.

Introspektion

Introspektion ist der Prozess der Analyse des Programms. Wenn Sie sagen können, was Programm tut, können Sie es nach Ihren Wünschen ändern. Obwohl einige Programmiersprachen keine Funktionen zur Codegenerierung oder Codeänderung unterstützen, ermöglichen sie höchstwahrscheinlich eine Introspektion.

Ein einfaches Beispiel für Introspektion wäre die Verwendung von typeof oder instanceof Operatoren in JavaScript. Die typeof gibt den aktuellen Datentyp eines Wertes (oder eines Ausdrucks, der einen Wert zurückgibt) zurück, während instanceoftrue oder false zurückgibt, wenn der LHS-Wert eine Instanz der RHS-Klasse ist. Lassen Sie uns sie in Aktion sehen.

(Einführung/Introspektion.js)

Im obigen Programm haben wir die Operatoren typeof und instanceof in der Funktion coerce verwendet, um den Datentyp eingehender value. Dies ist die grundlegende Demonstration der Introspektion. Eine Sprache, die speziell für die Metaprogrammierung entwickelt wurde, kann jedoch einige leistungsstarke Introspektionswerkzeuge bereitstellen.

Sie können den Operator in verwenden, um zu überprüfen, ob eine Eigenschaft im Objekt vorhanden ist. Die globale Funktion isNaN prüft, ob das Objekt NaN . Es gibt einige statische Methoden, die um den Object herum aufgebaut sind, um Werte des Object -Typs zu überprüfen, z. B. Object.isFrozen(value) um zu überprüfen, ob value eingefroren ist oder Object.keys(value) die Eigenschaftsnamen des value-Objekts.

Bis ES5 hatten wir diese Operatoren und diese Methoden zum Arbeiten. In ES2015 (ES6) hat JavaScript ein Reflect Objekt eingeführt, das einige statische Methoden bereitstellt (genau wie Object ), aber speziell für die Selbstbeobachtung entwickelt wurde. Da wir eine separate Lektion zu Reflect haben, werden diese Methoden dort diskutiert.

Fürbitte

Fürbitte ist der Prozess des Eingreifens in die JavaScript-Prozesse und des Änderns des Standardverhaltens des Prozesses. JavaScript bietet großartige Tools für die Fürbitte, von denen eines Proxy .

Die Proxy -Klasse wurde in ES2015 (ES6) eingeführt, um grundlegende JavaScript-Operationen um Objekte abzufangen (zu intervenieren), genau wie wir es oben gesehen haben, aber auf eine viel schönere Weise. Wir haben eine separate Lektion über Proxy (in Kürze), aber kurz gesagt, Proxy umschließt eine abfangbare Logik um ein Objekt.

var targetWithProxy = new Proxy(target, handler);

Hier ist das target Objekt und handler ist der Interceptor. Das handler ist ebenfalls ein einfaches JavaScript-Objekt, jedoch mit einigen aussagekräftigen Feldern. For example, handler.get would be a function that returns a custom value when target.prop (here, prop is any property) is accessed.

(introduction/proxy.js)

Proxy is a great way to provide abstractions over your not-so-public data. Im obigen Programm haben wir beispielsweise Abstraktionen über das target -Objekt bereitgestellt und angepasst, wie es sich der Öffentlichkeit präsentieren soll.

Einige Fürbitten waren auch in ES5 möglich, z. B. die Verwendung von getter und setters für Eigenschaftsdeskriptoren, aber dies würde zur Mutation des target Objekts führen. Proxy bietet eine viel sauberere Möglichkeit, Fürbitte zu leisten, ohne das ursprüngliche Objekt ändern zu müssen (target).

Modifikation

Modifikation bezieht sich auf die Modifikation des Programmverhaltens durch Mutation. Im Falle der Intercession haben wir nur die Standard-JavaScript-Prozesse abgefangen, indem wir eine Intercepting-Logik zwischen dem Ziel und dem Empfänger hinzugefügt haben, ohne das Ziel zu beschädigen. In diesem Fall Modifikation, ändern wir das Verhalten des Ziels selbst so passen den Empfänger.

Das Überschreiben einer Funktionsimplementierung wäre ein gutes Beispiel für eine Modifikation. Wenn sich eine Funktion beispielsweise so verhält, dass sie sich auf eine bestimmte Weise verhält, wir aber etwas anderes bedingt möchten, können wir dies tun, indem wir eine selbstübergeordnete Funktion entwerfen. Sehen wir uns ein Beispiel an.

(Einführung/Funktionsänderung.js)

Im obigen Beispiel haben wir eine Funktion erstellt, die sich mit einer neuen Funktionsimplementierung überschreibt. Dies wäre das härteste Beispiel für Modifikationen, aber wir haben andere, vielleicht aussagekräftigere Anwendungsfälle.

(Einführung/readonly-Objekt.js)

Im obigen Beispiel haben wir die Object.defineProperty() Methode verwendet, um den Standardeigenschaftsdeskriptor der name Eigenschaft zu ändern, um sie schreibgeschützt zu machen. Sie können auch die Object.freeze() Methode verwenden, um das gesamte Objekt zu sperren, um Mutationen zu vermeiden.

Einige Fürbitten können durch Modifikationen geschehen, wie Sie im obigen Beispiel sehen können. Durch Setzen von writable:false im Eigenschaftsdeskriptor des Objekts, also Mutieren des Objekts (interne Implementierung), haben wir die Wertzuweisungsoperation eingeleitet.

Wenn Sie mit der valueOf -Methode nicht vertraut sind, wird sie verwendet, um ein Objekt zu einem primitiven Wert zu zwingen. Wenn ich also ein Objekt habe und es die valueOf -Methode für sich selbst oder für seine Prototypkette hat, wird diese Methode von JavaScript aufgerufen, wenn Sie versuchen, eine arithmetische Operation darauf auszuführen. Standardmäßig hat Object die valueOf Methode, die sich selbst (das Objekt) zurückgibt.

(Einführung/valueof.js)

Wie Sie im obigen Beispiel sehen können, emp1/10 führte zu einem NaN da ein Objekt nicht wie natürliche Zahlen geteilt werden kann. Aber später haben wir hinzugefügt valueOf Methode auf Employee Klasse, die salary Wert des Objekts zurückgibt. Daher emp2/10 zurückgegeben 200 seit emp2.salary ist 200. Ebenso emp3/10 zurückgegeben 300 wie wir hinzugefügt haben valueOf Methode direkt auf der emp3.

Also greifen wir bei jedem Schritt im obigen Beispiel ein, wie ein Objekt einer Standard-JavaScript-Operation präsentiert wird, und ändern sein Verhalten nach unseren Wünschen. Dies ist nichts anderes als die Fürsprache.

In ES2015 (ES6)hat JavaScript einen neuen primitiven Datentyp eingeführt, der symbol . Es ist nichts, was wir zuvor gesehen haben und kann nicht in wörtlicher Form dargestellt werden. Es kann nur durch Aufrufen der Funktion Symbol erstellt werden.

var sym1 = Symbol();
var sym2 = Symbol();
var sym3 = Symbol('description'); // description for debugging aidsym1 === sym2 // false
sym1 === sym2 // falsetypeof sym1 // 'symbol'console.log( sym1 ); // 'Symbol()'
console.log( sym3 ); // 'Symbol(description)'

Kurz gesagt, es erzeugt eindeutige Werte, die auch als reguläre Objektschlüssel verwendet werden können, indem die Notation obj verwendet wird, wobei key ein Symbol wäre.

var key = Symbol();var obj = {
name: 'Ross',
: 200
};console.log( obj.name ); // 'Ross'
console.log( obj ); // 200
Object.keys(obj); // obj = 300;

Da sie eindeutig sind, gibt es keine Möglichkeit, versehentlich ein Duplikatsymbol zu erstellen. Jedes neue Symbol ist eindeutig (erstellt mit Symbol() ), dh wenn Sie dasselbe Symbol verwenden möchten, müssen Sie dieses in einer Variablen speichern und diese Variable übergeben, um auf dasselbe Symbol zu verweisen.

Im valueOf -Beispiel können Sie das Problem erkennen, wenn wir nicht vorsichtig oder bewusst sind. Da valueOf eine string Eigenschaft ist (wie in emp3 ), kann jeder sie versehentlich überschreiben oder jemand, der nichts über valueOf weiß, könnte planen, sie für seinen eigenen Gebrauch zu verwenden und zu denken: „Was ist im Namen?“.

Da Symbole auch als Objektschlüssel verwendet werden können, hat JavaScript einige globale Symbole bereitgestellt, die als Objektschlüssel für einige Standard-JavaScript-Operationen verwendet werden sollten. Da diese Symbole einem Entwickler gut bekannt sind, werden sie als „bekannte Symbole“ bezeichnet. Diese bekannten Symbole werden der Öffentlichkeit als statische Eigenschaften der Symbol -Funktion zur Verfügung gestellt.

Eines der bekannten Symbole ist Symbol.toPrimitive, das als Schlüssel des Objekts verwendet werden sollte, um seinen primitiven Wert zu erhalten. Ja, Sie denken richtig, es ist ein Ersatz der valueOf Methode und es wird bevorzugt.

(Einführung/symbol-toPrimitive.js)

💡 Die toPrimitive Methode gibt nicht nur einen Zahlenwert des Objekts zurück. Bitte lesen Sie die folgenden Lektionen, um mehr darüber zu erfahren.

JavaScript bietet viele dieser bekannten Symbole, um das standardmäßige JavaScript-Verhalten um Objekte herum abzufangen und zu ändern. Wir werden darüber und Symbole im Allgemeinen in der Symbolstunde sprechen.