En kort introduktion till Metaprogrammering i JavaScript
reflektion
reflektion, olikad ”kodgenerering”, är en process för att ändra språkets underliggande mekanik. Reflektion kan hända vid kompileringstid eller vid körning, men vi kommer att hålla fast vid runtime reflection när vi pratar om JavaScript, så kompileringstid reflektion kommer inte att vara möjligt. Men begrepp som diskuteras här kan också vara tillämpliga på ett sammanställt språk.
som vi har förstått att reflektionen handlar om att ändra språkets underliggande mekanik, har den delats in i tre huvudkategorier, nämligen. introspektion, förbön och modifiering.
introspektion
introspektion är processen att analysera programmet. Om du kan berätta vilket program som gör det kan du ändra det enligt dina önskemål. Även om vissa programmeringsspråk inte stöder kodgenerering eller kodmodifieringsfunktioner, men de tillåter troligen introspektion.
ett enkelt exempel på introspektion skulle vara att använda typeof
eller instanceof
operatörer i JavaScript. typeof
returnerar den aktuella datatypen för ett värde (eller ett uttryck som returnerar ett värde) medaninstanceof
returnerartrue
ellerfalse
om LHS-värdet är en instans av RHS-klassen. Låt oss se dem i aktion.
i ovanstående program har vi använt typeof
och instanceof
operatörer i funktionen coerce
för att sniffa datatypen för inkommande value
. Detta är den grundläggande demonstrationen av introspektion. Ett språk som är särskilt utformat för metaprogrammering kan dock ge några kraftfulla introspektionsverktyg.
Du kan använda operatornin
för att kontrollera om det finns en egenskap i objektet. Den globala funktionenisNaN
kontrollerar om objektet ärNaN
. Det finns några statiska metoder byggda runt Object
för att inspektera värden för Object
typ som Object.isFrozen(value)
för att kontrollera om value
är fryst eller Object.keys(value)
för att få egenskapsnamnen på objektet value
.
fram till ES5 hade vi dessa operatörer och dessa metoder att arbeta med. I ES2015 (ES6) introducerade JavaScript Reflect
objekt som ger några statiska metoder (precis som Object
) men speciellt utformad för introspektion. Eftersom vi har en separat lektion om Reflect
diskuteras dessa metoder där.
Intercession
Intercession är processen att ingripa i JavaScript-processerna och modifiera processens standardbeteende. JavaScript ger bra verktyg för förbön varav en är Proxy
.
Proxy
klassen introducerades i ES2015 (ES6) för att avlyssna (ingripa) grundläggande JavaScript-operationer runt objekt precis som vi har sett ovan men på ett mycket trevligare sätt. Vi har en separat lektion om Proxy
(kommer snart) men i ett nötskal, Proxy
sveper en avlyssningsbar logik runt ett objekt.
var targetWithProxy = new Proxy(target, handler);
Här är target
objekt och handler
är interceptorn. handler
är också ett vanligt JavaScript-objekt men med några meningsfulla fält. 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. I ovanstående program har vi till exempel tillhandahållit abstraktioner över target
objekt och anpassat hur det ska presentera sig för allmänheten.
vissa förbön var också möjliga i ES5 som att använda getter
och setters
på egenskapsbeskrivningar men det skulle resultera i mutationen av target
objekt. Proxy
ger ett mycket renare sätt att uppnå förbön utan att behöva ändra det ursprungliga objektet (target
).
modifiering
modifiering avser modifiering av programbeteendet genom mutation. När det gäller förbön avlyssnade vi bara de vanliga JavaScript-processerna genom att lägga till en avlyssningslogik mellan målet och mottagaren utan att skada målet. I detta fall modifiering, vi ändrar beteendet hos själva målet så passar mottagaren.
att åsidosätta en funktionsimplementering skulle vara ett bra exempel på modifiering. Till exempel, om en funktion är utformad för att bete sig på ett visst sätt, men vi vill ha något annat villkorligt, kan vi göra det genom att utforma en självövergripande funktion. Låt oss se ett exempel.
i exemplet ovan har vi skapat en funktion som åsidosätter sig med en ny funktionsimplementering. Detta skulle vara det hårdaste exemplet på modifiering men vi har andra, kanske mer meningsfulla användningsfall.
i exemplet ovan har vi använtObject.defineProperty()
metod för att ändra standardegenskapsbeskrivaren för egenskapenname
för att göra den skrivskyddad. Du kan också använda metoden Object.freeze()
för att låsa hela objektet för att undvika mutationer.
vissa förbön kan ske genom ändringar som du kan från ovanstående exempel. Genom att ställa in writable:false
I objektets egenskapsbeskrivare och därför mutera objektet (intern implementering) har vi inlett värdetilldelningsoperationen.
om du inte är bekant med metodenvalueOf
används den för att tvinga ett objekt till ett primitivt värde. Så om jag har ett objekt och det har valueOf
– metoden på sig själv eller på dess prototypkedja, kallas den här metoden av JavaScript när du försöker utföra en aritmetisk operation på den. Som standardObject
harvalueOf
metod som returnerar sig själv (objektet).
som du kan se i exemplet ovan resulteradeemp1/10
I ettNaN
eftersom ett objekt inte kan delas som naturliga tal. Men senare har vi lagt tillvalueOf
metod påEmployee
klass som returnerarsalary
objektets värde. Därföremp2/10
returnerade200
eftersomemp2.salary
är200
. På samma sätt emp3/10
returnerade 300
som vi har lagt till valueOf
metod direkt på emp3
.
så vid varje steg i ovanstående exempel ingriper vi hur ett objekt presenteras för en vanlig JavaScript-operation och ändrar dess beteende till våra likings. Detta är inget annat än förbön.
i ES2015 (ES6) har JavaScript introducerat en ny primitiv datatyp som ärsymbol
. Det är inget som vi har sett tidigare och kan inte representeras i bokstavlig form. Det kan bara konstrueras genom att anropa funktionen Symbol
.
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 ett nötskal producerar det unika värden som också kan användas som vanliga objektnycklar med obj
notation där key
skulle vara en symbol.
var key = Symbol();var obj = {
name: 'Ross',
: 200
};console.log( obj.name ); // 'Ross'
console.log( obj ); // 200
Object.keys(obj); // obj = 300;
eftersom de är unika finns det inget sätt att skapa en dubblettsymbol av misstag. Varje ny symbol är unik (skapad med Symbol()
) vilket innebär att om du vill använda samma symbol måste du lagra den i en variabel och skicka den variabeln runt för att hänvisa till samma symbol.
i exemplet valueOf
kan du upptäcka problemet om vi inte är försiktiga eller medvetna. Eftersom valueOf
är en string
egenskap (som i emp3
) kan vem som helst åsidosätta det av misstag eller någon som inte vet om valueOf
kan planera att använda den för eget bruk och tänka ”Vad är i namnet?”.
eftersom symboler också kan användas som objektnycklar har JavaScript tillhandahållit några globala symboler som ska användas som objektnycklar för vissa vanliga JavaScript-operationer. Eftersom dessa symboler är välkända för en utvecklare kallas de”välkända symboler”. Dessa välkända symboler exponeras för allmänheten som de statiska egenskaperna hos Symbol
funktion.
en av de välkända symbolerna är Symbol.toPrimitive
som ska användas som objektets nyckel för att få sitt primitiva värde. Ja, du tänker rätt, det är en ersättning avvalueOf
metod och det är att föredra.
toPrimitive
metoden gör mer än att bara returnera ett talvärde för objektet. Läs symbolerna lektioner för att veta mer om det.
JavaScript ger många sådana välkända symboler för att fånga upp och ändra standard JavaScript beteende runt objekt. Vi kommer att prata om detta och symboler i allmänhet i Symbollektionen.