En kort introduktion til metaprogrammering i JavaScript
refleksion
refleksion, i modsætning til “kodegenerering”, er en proces til at ændre sprogets underliggende mekanik. Refleksion kan ske ved kompileringstid eller ved kørsel, men vi holder os til runtime reflection, da vi taler om JavaScript, så kompileringstidsreflektion vil ikke være mulig. Imidlertid kan begreber, der diskuteres her, også gælde for et kompileret sprog.
da vi har forstået, at refleksionen handler om at ændre sprogets underliggende mekanik, er den opdelt i tre hovedkategorier, nemlig. introspektion, forbøn og modifikation.
introspektion
introspektion er processen med at analysere programmet. Hvis du kan fortælle, hvad programmet gør, kan du ændre det som pr dine likings. Selvom nogle programmeringssprog ikke understøtter kodegenerering eller kodemodifikationsfunktioner, men de tillader sandsynligvis introspektion.
et simpelt eksempel på introspektion ville brugetypeof
ellerinstanceof
operatører i JavaScript. typeof
returnerer den aktuelle datatype for en værdi (eller et udtryk, der returnerer en værdi), mens instanceof
returnerer true
eller false
hvis LHS-værdi er en forekomst af RHS-klasse. Lad os se dem i aktion.
i ovenstående program har vi brugt typeof
og instanceof
operatører i coerce
funktion til at snuse datatypen for indgående value
. Dette er den grundlæggende demonstration af introspektion. Et sprog, der er specielt designet til metaprogrammering, kan dog give nogle kraftfulde introspektionsværktøjer.
Du kan brugein
operatoren til at kontrollere, om der findes en egenskab i objektet. isNaN
global funktion kontrollerer, om objektet erNaN
. Der er nogle statiske metoder bygget op omkring Object
for at inspicere værdier af Object
typen som Object.isFrozen(value)
for at kontrollere, om value
er frosset eller Object.keys(value)
for at få egenskabsnavnene på value
objektet.
indtil ES5 havde vi disse operatører og disse metoder til at arbejde med. I ES2015 (ES6) introducerede JavaScript Reflect
objekt, der giver nogle statiske metoder (ligesom Object
), men specielt designet til introspektion. Da vi har en separat lektion om Reflect
, diskuteres disse metoder der.
Forbøn
Forbøn er processen med at gribe ind i JavaScript-processerne og ændre procesens standardadfærd. JavaScript giver gode værktøjer til forbøn hvoraf den ene er Proxy
.
Proxy
klassen blev introduceret i ES2015 (ES6) for at opfange (gribe ind) grundlæggende JavaScript-operationer omkring objekter, ligesom vi har set ovenfor, men på en meget pænere måde. Vi har en separat lektion om Proxy
(kommer snart), men i en nøddeskal, Proxy
vikler en interceptabel logik omkring et objekt.
var targetWithProxy = new Proxy(target, handler);
Her er target
Er objekt og handler
er interceptor. handler
er også et almindeligt JavaScript-objekt, men med nogle meningsfulde felter. For example, handler.get
would be a function that returns a custom value when target.prop
(here, prop
is any property) is accessed.
Proxy is a great way to provide abstractions over your not-so-public data. For eksempel har vi i ovenstående program leveret abstraktioner over target
objekt og tilpasset, hvordan det skal præsentere sig for offentligheden.
nogle forbøn var også mulige i ES5, såsom at bruge getter
og setters
på ejendomsdeskriptorer, men det ville resultere i mutationen af target
objekt. Proxy
giver en meget renere måde at opnå forbøn på uden at skulle ændre det originale objekt (target
).
modifikation
modifikation refererer til modifikationen af programadfærden gennem mutation. I tilfælde af forbøn opfangede vi kun standard JavaScript-processerne ved at tilføje en aflytningslogik mellem målet og modtageren uden at skade målet. I dette tilfælde ændring, er vi ændre opførslen af målet selv så passer modtageren.
tilsidesættelse af en funktionsimplementering ville være et godt eksempel på ændring. For eksempel, hvis en funktion er designet til at opføre sig på en bestemt måde, men vi ønsker noget andet betinget, kan vi gøre det ved at designe en selvoverstyrende funktion. Lad os se et eksempel.
i ovenstående eksempel har vi oprettet en funktion, der tilsidesætter sig selv med en ny funktionsimplementering. Dette ville være det hårdeste eksempel på ændring, men vi har andre, måske mere meningsfulde brugssager.
i ovenstående eksempel har vi brugt Object.defineProperty()
metode til at ændre standardegenskabsbeskrivelsen for name
egenskaben for at gøre den skrivebeskyttet. Du kan også bruge Object.freeze()
– metoden til at låse hele objektet for at undgå mutationer.
nogle forbøn kan ske gennem ændringer, som du kan fra ovenstående eksempel. Ved at indstille writable:false
i objektets egenskabsbeskrivelse, derfor muterer objektet (intern implementering), har vi påbegyndt værditildelingsoperationen.
Hvis du ikke er bekendt medvalueOf
– metoden, bruges den til at tvinge et objekt til en primitiv værdi. Så hvis jeg har et objekt, og det har valueOf
metode på sig selv eller på sin prototype kæde, så kaldes denne metode af JavaScript, når du forsøger at udføre en aritmetisk operation på den. Som standard Object
har valueOf
metode, der returnerer sig selv (objektet).
som du kan se i ovenstående eksempel,emp1/10
resulterede i enNaN
da et objekt ikke kan opdeles som naturlige tal. Men senere har vi tilføjet valueOf
metode på Employee
klasse, der returnerer salary
værdien af objektet. Derforemp2/10
returnerede200
sidenemp2.salary
er200
. Tilsvarendeemp3/10
returnerede300
som vi har tilføjetvalueOf
metode direkte påemp3
.
så ved hvert trin i ovenstående eksempel griber vi ind, hvordan et objekt præsenteres for en standard JavaScript-operation og ændrer dets adfærd til vores smag. Dette er intet andet end forbøn.
i ES2015 (ES6) har JavaScript introduceret en ny primitiv datatype, som ersymbol
. Det er intet som vi har set før og kan ikke repræsenteres i bogstavelig form. Det kan kun konstrueres ved at kalde Symbol
– funktionen.
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)'
i en nøddeskal producerer den unikke værdier, der også kan bruges som almindelige objekttaster ved hjælp afobj
notation hvorkey
ville være et symbol.
var key = Symbol();var obj = {
name: 'Ross',
: 200
};console.log( obj.name ); // 'Ross'
console.log( obj ); // 200
Object.keys(obj); // obj = 300;
da de er unikke, er der ingen måde at oprette et duplikatsymbol ved et uheld. Hvert nyt symbol er unikt (oprettet ved hjælp af Symbol()
), hvilket betyder, at hvis du vil bruge et samme symbol, skal du gemme det i en variabel og videregive den variabel rundt for at henvise til det samme symbol.
i eksempletvalueOf
kan du se problemet, hvis vi ikke er forsigtige eller Opmærksomme. Siden valueOf
er en string
ejendom (som i emp3
) , kan enhver tilsidesætte det ved et uheld eller nogen, der ikke ved om valueOf
kunne planlægge at bruge det til eget brug og tænke “Hvad er det, der er i navnet?”.
da symboler også kan bruges som objektnøgler, har JavaScript leveret nogle globale symboler, der skal bruges som objektnøgler til nogle standard JavaScript-operationer. Da disse symboler er velkendte for en udvikler, kaldes de”velkendte symboler”. Disse velkendte symboler udsættes for offentligheden som de statiske egenskaber ved Symbol
funktion.
et af de velkendte symboler er Symbol.toPrimitive
som skal bruges som objektets nøgle for at opnå sin primitive værdi. Ja, du tænker rigtigt, det er en erstatning af valueOf
metode, og det er foretrukket.
ret
toPrimitive
metode gør mere end blot at returnere en talværdi af objektet. Læs symbolerne lektioner for at vide mere om det.
JavaScript giver mange sådanne velkendte symboler til at opfange og ændre standard JavaScript-adfærd omkring objekter. Vi vil tale om dette og symboler generelt i Symbolundervisningen.