Une brève introduction à la métaprogrammation en JavaScript
Réflexion
La réflexion, unliked « génération de code », est un processus permettant de changer la mécanique sous-jacente du langage. La réflexion peut se produire au moment de la compilation ou à l’exécution, mais nous nous en tiendrons à la réflexion d’exécution car nous parlons de JavaScript, donc la réflexion au moment de la compilation ne sera pas possible. Cependant, les concepts discutés ici pourraient également s’appliquer à un langage compilé.
Comme nous l’avons compris, la réflexion consiste à changer les mécanismes sous-jacents du langage, il a été divisé en trois grandes catégories, à savoir. introspection, intercession et modification.
Introspection
L’introspection est le processus d’analyse du programme. Si vous pouvez dire ce que fait le programme, vous pouvez le modifier selon vos goûts. Même si certains langages de programmation ne prennent pas en charge les fonctionnalités de génération ou de modification de code, ils permettent très probablement l’introspection.
Un exemple simple d’introspection serait d’utiliser les opérateurs typeof
ou instanceof
en JavaScript. Le typeof
renvoie le type de données courant d’une valeur (ou une expression qui renvoie une valeur) tandis que instanceof
renvoie true
ou false
si la valeur LHS est une instance de la classe RHS. Voyons-les en action.
Dans le programme ci-dessus, nous avons utilisé des opérateurs typeof
et instanceof
dans la fonction coerce
pour renifler le type de données de value
. C’est la démonstration de base de l’introspection. Cependant, un langage spécialement conçu pour la métaprogrammation pourrait fournir de puissants outils d’introspection.
Vous pouvez utiliser l’opérateur in
pour vérifier si une propriété existe dans l’objet. La fonction globale isNaN
vérifie si l’objet est NaN
. Il existe des méthodes statiques construites autour du type Object
pour inspecter les valeurs du type Object
telles que Object.isFrozen(value)
pour vérifier si value
est gelé ou Object.keys(value)
pour obtenir les noms de propriété de l’objet value
.
Jusqu’à ES5, nous avions ces opérateurs et ces méthodes avec lesquels travailler. Dans ES2015 (ES6), JavaScript a introduit un objet Reflect
qui fournit des méthodes statiques (tout comme Object
) mais spécialement conçu pour l’introspection. Puisque nous avons une leçon distincte sur Reflect
, ces méthodes y sont discutées.
Intercession
Intercession est le processus d’intervention dans les processus JavaScript et de modification du comportement standard du processus. JavaScript fournit d’excellents outils pour l’intercession, dont l’un est Proxy
.
La classe Proxy
a été introduite dans ES2015 (ES6) pour intercepter (intervenir) des opérations JavaScript de base autour d’objets comme nous l’avons vu ci-dessus mais d’une manière beaucoup plus agréable. Nous avons une leçon séparée sur Proxy
(à venir bientôt) mais en un mot, Proxy
enveloppe une logique interceptable autour d’un objet.
var targetWithProxy = new Proxy(target, handler);
Ici, le target
est un objet et handler
est l’intercepteur. Le handler
est également un objet JavaScript simple mais avec des champs significatifs. 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. Par exemple, dans le programme ci-dessus, nous avons fourni des abstractions sur l’objet target
et personnalisé comment il doit se présenter au public.
Une certaine intercession était également possible dans ES5, par exemple en utilisant getter
et setters
sur les descripteurs de propriété, mais cela entraînerait la mutation de l’objet target
Proxy
fournit un moyen beaucoup plus propre d’obtenir une intercession sans avoir à modifier l’objet d’origine (target
).
Modification
La modification fait référence à la modification du comportement du programme par mutation. Dans le cas de l’intercession, nous n’avons intercepté que les processus JavaScript standard en ajoutant une logique d’interception entre la cible et le récepteur sans nuire à la cible. Dans ce cas de modification, nous modifions le comportement de la cible elle-même afin de convenir au récepteur.
Remplacer une implémentation de fonction serait un bon exemple de modification. Par exemple, si une fonction est conçue pour se comporter d’une certaine manière, mais que nous voulons autre chose de manière conditionnelle, nous pouvons le faire en concevant une fonction auto-dominante. Voyons un exemple.
Dans l’exemple ci-dessus, nous avons créé une fonction qui se substitue à une nouvelle implémentation de fonction. Ce serait l’exemple de modification le plus sévère, mais nous avons d’autres cas d’utilisation, peut-être plus significatifs.
Dans l’exemple ci-dessus, nous avons utilisé la méthode Object.defineProperty()
pour modifier le descripteur de propriété par défaut de la propriété name
afin de la rendre en lecture seule. Vous pouvez également utiliser la méthode Object.freeze()
pour verrouiller l’objet entier afin d’éviter toute mutation.
Certaines intercessions peuvent se produire par des modifications comme vous pouvez le faire à partir de l’exemple ci-dessus. En définissant writable:false
dans le descripteur de propriété de l’objet, donc en mutant l’objet (implémentation interne), nous avons initié l’opération d’attribution de valeur.
Si vous n’êtes pas familier avec la méthode valueOf
, elle est utilisée pour contraindre un objet à une valeur primitive. Donc, si j’ai un objet et qu’il a la méthode valueOf
sur lui-même ou sur sa chaîne prototype, cette méthode est appelée par JavaScript lorsque vous essayez d’effectuer une opération arithmétique dessus. Par défaut, Object
a la méthode valueOf
qui retourne elle-même (l’objet).
Comme vous pouvez le voir dans l’exemple ci-dessus, emp1/10
a entraîné un NaN
car un objet ne peut pas être divisé comme des nombres naturels. Mais plus tard, nous avons ajouté la méthode valueOf
sur la classe Employee
qui renvoie la valeur salary
de l’objet. Par conséquent, emp2/10
retourné 200
depuis emp2.salary
est 200
. De même, emp3/10
a renvoyé 300
car nous avons ajouté la méthode valueOf
directement sur la emp3
.
Donc, à chaque étape de l’exemple ci-dessus, nous intervenons sur la façon dont un objet est présenté à une opération JavaScript standard et modifions son comportement à nos goûts. Ce n’est rien d’autre que l’intercession.
Dans ES2015 (ES6), JavaScript a introduit un nouveau type de données primitif qui est symbol
. Ce n’est rien comme nous l’avons vu auparavant et ne peut pas être représenté sous une forme littérale. Il ne peut être construit qu’en appelant la fonction 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)'
En un mot, il produit des valeurs uniques qui peuvent également être utilisées comme clés d’objet régulières en utilisant la notation obj
où key
serait un symbole.
var key = Symbol();var obj = {
name: 'Ross',
: 200
};console.log( obj.name ); // 'Ross'
console.log( obj ); // 200
Object.keys(obj); // obj = 300;
Comme ils sont uniques, il n’y a aucun moyen de créer un symbole en double par accident. Chaque nouveau symbole est unique (créé en utilisant Symbol()
), ce qui signifie que si vous souhaitez utiliser un même symbole, vous devez le stocker dans une variable et passer cette variable pour faire référence au même symbole.
Dans l’exemple valueOf
, vous pouvez repérer le problème si nous ne sommes pas prudents ou conscients. Étant donné que valueOf
est une propriété string
(comme dans emp3
), n’importe qui peut la remplacer accidentellement ou quelqu’un qui ne connaît pas valueOf
pourrait planifier de l’utiliser pour son propre usage en pensant « ce qui est au nom ? ».
Comme les symboles peuvent également être utilisés comme clés d’objet, JavaScript a fourni des symboles globaux qui devraient être utilisés comme clés d’objet pour certaines opérations JavaScript standard. Étant donné que ces symboles sont bien connus d’un développeur, ils sont appelés « symboles bien connus ». Ces symboles bien connus sont exposés au public en tant que propriétés statiques de la fonction Symbol
.
L’un des symboles bien connus est Symbol.toPrimitive
qui doit être utilisé comme clé de l’objet pour obtenir sa valeur primitive. Oui, vous pensez bien, c’est un remplacement de la méthode valueOf
et c’est préféré.
💡 La méthode
toPrimitive
fait plus que simplement renvoyer une valeur numérique de l’objet. Veuillez lire les leçons sur les symboles pour en savoir plus à ce sujet.
JavaScript fournit de nombreux symboles bien connus pour intercepter et modifier le comportement JavaScript par défaut autour des objets. Nous en parlerons et des symboles en général dans la leçon sur les symboles.