krótkie wprowadzenie do Metaprogramowania w JavaScript
odbicie
odbicie, w przeciwieństwie do „generowania kodu”, jest procesem zmieniającym podstawową mechanikę języka. Odbicie może nastąpić w czasie kompilacji lub w trybie runtime, ale będziemy trzymać się odbicia w trybie runtime, ponieważ mówimy o JavaScript, więc odbicie w czasie kompilacji nie będzie możliwe. Jednak koncepcje omawiane tutaj mogą mieć zastosowanie również do skompilowanego języka.
ponieważ zrozumieliśmy, że refleksja polega na zmianie podstawowej mechaniki języka, została ona podzielona na trzy główne kategorie, a mianowicie. introspekcja, wstawiennictwo i modyfikacja.
introspekcja
introspekcja jest procesem analizy programu. Jeśli wiesz, co robi program, możesz go zmodyfikować według swoich upodobań. Mimo że niektóre języki programowania nie obsługują funkcji generowania lub modyfikacji kodu, ale najprawdopodobniej pozwalają na introspekcję.
prostym przykładem introspekcji byłoby użycietypeof
lubinstanceof
operatorów w JavaScript. typeof
zwraca bieżący typ danych wartości (lub wyrażenia, które zwraca wartość), podczas gdy instanceof
zwraca true
lub false
, jeśli wartość LHS jest instancją klasy RHS. Zobaczmy ich w akcji.
w powyższym programie użyliśmy typeof
I instanceof
operatorów w funkcji coerce
do wąchania typu danych przychodzących value
. Jest to podstawowa demonstracja introspekcji. Jednak język specjalnie zaprojektowany do metaprogramowania może dostarczyć potężnych narzędzi do introspekcji.
możesz użyć in
, aby sprawdzić, czy właściwość istnieje w obiekcie. Funkcja globalnaisNaN
sprawdza, czy obiekt jestNaN
. Istnieje kilka statycznych metod zbudowanych wokół Object
, aby sprawdzić wartości typu Object
, takich jak Object.isFrozen(value)
, aby sprawdzić, czy value
jest zamrożone lub Object.keys(value)
aby uzyskać nazwy właściwości obiektu value
.
do ES5 mieliśmy te operatory i te metody do pracy. W ES2015 (ES6) JavaScript wprowadził Reflect
obiekt dostarczający pewne metody statyczne (podobnie jak Object
), ale specjalnie zaprojektowany do introspekcji. Ponieważ mamy osobną lekcję na temat Reflect
, metody te są tam omówione.
wstawiennictwo
wstawiennictwo jest procesem ingerencji w procesy JavaScript i modyfikowania standardowego zachowania procesu. JavaScript zapewnia świetne narzędzia do wstawiennictwa jednym z nich jest Proxy
.
KlasaProxy
została wprowadzona w ES2015 (ES6) do przechwytywania (ingerowania) podstawowych operacji JavaScript wokół obiektów, tak jak widzieliśmy powyżej, ale w znacznie ładniejszy sposób. Mamy osobną lekcję na tematProxy
(wkrótce), ale w skrócie,Proxy
zawija przechwytywalną logikę wokół obiektu.
var targetWithProxy = new Proxy(target, handler);
tutajtarget
jest obiektem, ahandler
jest przechwytywaczem. handler
jest również zwykłym obiektem JavaScript, ale z pewnymi znaczącymi polami. 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. Na przykład w powyższym programie podaliśmy abstrakty nadtarget
obiekt i dostosowaliśmy sposób, w jaki powinien się prezentować publicznie.
niektóre wstawiennictwo było możliwe również w ES5, na przykład użyciegetter
Isetters
na deskryptorach właściwości, ale spowodowałoby to mutację obiektutarget
Proxy
zapewnia znacznie czystszy sposób na uzyskanie wstawiennictwa bez konieczności modyfikowania oryginalnego obiektu (target
).
modyfikacja
modyfikacja odnosi się do modyfikacji zachowania programu poprzez mutację. W przypadku wstawiennictwa przechwyciliśmy tylko standardowe procesy JavaScript, dodając logikę przechwytującą między celem a odbiornikiem bez szkody dla celu. W tym przypadku modyfikacja, zmieniamy zachowanie samego celu tak dopasować odbiornika.
nadpisanie implementacji funkcji byłoby dobrym przykładem modyfikacji. Na przykład, jeśli funkcja jest zaprojektowana, aby zachowywać się w określony sposób, ale chcemy czegoś innego warunkowo, możemy to zrobić, projektując funkcję samoistną. Zobaczmy przykład.
w powyższym przykładzie stworzyliśmy funkcję, która nadpisuje się nową implementacją funkcji. Byłby to najsurowszy przykład modyfikacji, ale mamy inne, być może bardziej znaczące przypadki użycia.
w powyższym przykładzie użyliśmyObject.defineProperty()
metody do zmiany domyślnego deskryptora właściwościname
w celu uczynienia go tylko do odczytu. Możesz również użyć metodyObject.freeze()
, aby zablokować cały obiekt, aby uniknąć jakichkolwiek mutacji.
niektóre wstawiennictwo może się zdarzyć poprzez modyfikacje, jak można z powyższego przykładu. Ustawiając writable:false
w deskryptorze właściwości obiektu, a więc mutując obiekt (wewnętrzna implementacja), uruchomiliśmy operację przypisania wartości.
Jeśli nie znasz metody valueOf
, jest ona używana do wymuszania obiektu na prymitywnej wartości. Jeśli więc mam obiekt i ma onvalueOf
metodę na sobie lub na swoim prototypowym łańcuchu, to ta metoda jest wywoływana przez JavaScript, gdy próbujesz wykonać na nim operację arytmetyczną. Domyślnie Object
posiada metodę valueOf
, która zwraca samą siebie (obiekt).
jak widać w powyższym przykładzie,emp1/10
spowodowałoNaN
, ponieważ obiekt nie może być podzielony jak liczby naturalne. Ale później dodaliśmyvalueOf
metodę naEmployee
klasę, która zwracasalary
wartość obiektu. Dlategoemp2/10
zwrócił200
, ponieważemp2.salary
jest200
. Podobnie, emp3/10
zwrócił 300
jak dodaliśmy valueOf
metoda bezpośrednio na emp3
.
Tak więc na każdym kroku w powyższym przykładzie ingerujemy w sposób, w jaki obiekt jest prezentowany do standardowej operacji JavaScript i zmieniamy jego zachowanie na nasze upodobania. To tylko wstawiennictwo.
w ES2015 (ES6) JavaScript wprowadził nowy prymitywny typ danych, który jestsymbol
. To nic takiego, jak widzieliśmy wcześniej i nie może być reprezentowane w dosłownej formie. Można ją skonstruować tylko przez wywołanie funkcjiSymbol
.
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)'
w skrócie, tworzy unikalne wartości, które mogą być również używane jako zwykłe klucze obiektów przy użyciu notacjiobj
, gdziekey
byłby symbolem.
var key = Symbol();var obj = {
name: 'Ross',
: 200
};console.log( obj.name ); // 'Ross'
console.log( obj ); // 200
Object.keys(obj); // obj = 300;
ponieważ są one unikalne, nie ma możliwości utworzenia duplikatu symbolu przez przypadek. Każdy nowy symbol jest unikalny (utworzony za pomocą Symbol()
), co oznacza, że jeśli chcesz użyć tego samego symbolu, musisz zapisać go w zmiennej i przekazać tę zmienną, aby odnosić się do tego samego symbolu.
w przykładzievalueOf
możesz zauważyć problem, jeśli nie jesteśmy ostrożni lub świadomi. PonieważvalueOf
jest właściwościąstring
(tak jak w emp3
), każdy może ją nadpisać przypadkowo lub ktoś, kto nie wie o valueOf
może zaplanować jej użycie na własny użytek, myśląc ” co jest w imieniu?”.
ponieważ symbole mogą być również używane jako klucze obiektów, JavaScript dostarczył kilka globalnych symboli, które powinny być używane jako klucze obiektów dla niektórych standardowych operacji JavaScript. Ponieważ symbole te są dobrze znane deweloperowi, nazywane są”dobrze znanymi symbolami”. Te dobrze znane symbole są udostępniane publicznie jako statyczne właściwości funkcjiSymbol
.
jednym z dobrze znanych symboli jest Symbol.toPrimitive
, który powinien być użyty jako klucz obiektu w celu uzyskania jego pierwotnej wartości. Tak, dobrze myślisz, jest to zamiennik metodyvalueOf
I jest preferowany.
💡 metoda
toPrimitive
nie tylko zwraca wartość liczbową obiektu. Przeczytaj lekcje symboli, aby dowiedzieć się więcej na ten temat.
JavaScript zapewnia wiele takich dobrze znanych symboli do przechwytywania i modyfikowania domyślnego zachowania JavaScript wokół obiektów. Omówimy to i symbole w ogólności w lekcji Symbole.