Articles

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. typeofreturnerar den aktuella datatypen för ett värde (eller ett uttryck som returnerar ett värde) medaninstanceofreturnerartrueellerfalse om LHS-värdet är en instans av RHS-klassen. Låt oss se dem i aktion.

(introduktion / introspektion.JS)

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.

(introduction/proxy.js)

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.

(introduktion / funktionsmodifiering.js)

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.

(introduktion / readonly-objekt.js)

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).

(introduktion / valueof.js)

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.

(introduktion / symbol-toPrimitive.JS)

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.