Proxy en JavaScript: exemples simples
Outil de simplification du code, le proxy tend à séparer l'essentiel de l'application de l'accessoire.
Les proxies procurent un moyen de contrôler l'accès aux fonctions et objets. Cela simplifie la lecture d'un programme en reléguant tous les contrôles à un objet proxy alors qu'on peut utiliser l'objet de la façon la plus simple possible.
On crée un proxy avec cette instruction:
var p = new Proxy(sujet, interface)
- Le sujet peut être une variable, un objet, une fonction.
- L'interface est un objet où on définit une série d'actions à appliquer, selon un type prédéfini: set, get, apply, etc... On appelle ces actions des trappes, comme dans les systèmes d'exploitation.
On peut alors substituer le proxy à l'objet ou la fonction dans chaque référence à cet objet ou cette fonction. Chaque trappe intercepte un type d'opération.
Code du test:
var ptest = new Proxy({}, {})
if(ptest instanceof Object) document.write("Proxy supporté!")
Get
Intercepte la lecture des propriétés d'un objet, ou la valeur de retour d'une fonction.
var d = { "a": 1, "b": 2 }
var pget = new Proxy(
d, {
get: function(y, idx) {
return y[idx] * 10
}
}
)
pget.c = 3
for(var z in pget) {
document.write(z + ":")
document.write(pget[z])
}
Set
Intercepte l'assignement d'une valeur à une propriété ou l'appel d'une fonction.
var d3 = { "a": 1, "b": 2 }
var pset = new Proxy(
d3, {
set: function(y, idx, value) {
y[idx] = value * 10
}
}
)
pset.c = 3
for(var z in pset) {
document.writez + ":" )
document.write(pset[z])
}
Has
Modifie la liste des propriétés d'un objet telle qu'elle est vue lors d'un accès à celui-ci, donc la liste des propriétés accessibles. Elle retourne la valeur booléenne true si l'on veut que la propriété soit accessible, ou false autrement, que la clé soit présente ou non dans l'objet originel.
var phas = new Proxy({}, {
has: function(target, key) {
if(key == "a") return false;
return true;
}
}
)
var phasa = ("a" in phas)
var phasb = ("b" in phas)
document.write("a in d: " + phasa)
document.write("b in d: " + phasb)
Apply
Permet d'appeler le proxy avec une liste de paramètres. Intercepte aussi les méthodes apply, call et Reflect.apply lorsque le proxy est déclaré avec une fonction.
Pour mieux comprendre la trappe apply, il convient d'abord de connaître la méthode JavaScript apply.
Cette méthode à pour paramètre une valeur this et un tableau. Le premier paramètre substitue un objet désigné à l'objet qui contient la fonction. Si on appelle la fonction dans une fenêtre, l'objet fenêtre sera remplacé par un autre objet. On passe null pour ne pas changer l'objet contexte.
Le second paramètre est un tableau qui se substitue aux paramètres d'appel de la fonction.
function methodDemo(a, b){
var str = "Objet: " + this + "<br>";
for (var i in methodDemo.arguments) {
str += "argument: " + methodDemo.arguments[i] + "<br>";
}
return str;
}
document.write("Appel direct de la function:");
document.write(methodDemo(10, 20));
document.write("Appel par apply:");
document.write(methodDemo.apply(null, [ "dix", "vingt" ]));
Cela ne fonctionne qui si l'on accède à la liste des arguments par la propriété arguments. Donc on doit prévoir dans la définition un traitement particulier pour le cas où cette méthode sera utilisée.
C'est différent avec la trappe apply puisque l'on définit une fonction spéciale supplémentaire.
Celle-ci a trois paramètres:
- Le nom de la fonction pour laquelle on créé un proxy.
- L'objet contexte quand on change de contexte. Null si la fonction reste dans son contexte.
- Une liste d'arguments que l'on passe à la fonction cible quand on l'appelle.
Puis on peut utiliser le proxy de trois façons différentes.
- proxy(arguments)
Il s'agit des arguments passés à la fonction, séparés par une virgule. - proxy.apply(null, [ arguments ])
On donne un tableau comme liste d'arguments. - proxy.call(null, arg1, arg2, etc...)
On donne une liste d'arguments.
On peut ajouter un paramètre d'object de contexte avec la méthode call ou apply. Le paramètre est null comme ci-dessus quand on reste dans le contexte, et alors les méthodes call et apply sont superflues.
var papp = new Proxy(methodDemo, {
apply: function(methodDemo, ctx, args) {
return methodDemo.apply(ctx, args)
}
})
document.write(papp(100, 200))
Les arguments passés par le proxy se substituent aux arguments de la méthode apply de methodDemo.
Construct
La trappe construct intercepte les commandes new et Reflect.construct(). Elle retourne un object.
Les paramètres sont:
- L'objet originel.
- La liste des arguments du nouveau constructeur.
- Le constructeur de l'objet originel en option.
var pconst = new Proxy(function() {}, {
construct: function(objCible, args, ancienConstructeur) {
return { valeur : args[0] + " à tous" }
}
})
document.write(JSON.stringify(new pconst("Salut "), null, ' '))
Liste de toutes les trappes de proxy
Selon la spécification ECMAScript 262:
- apply.
- construct.
- deleteProperty.
- defineProperty.
- enumerate.
- get.
- getPrototypeOf.
- getOwnPropertyDescriptor.
- has.
- isExtensible.
- ownKeys.
- preventExtensions.
- set.
- setPrototypeOf.
Peut-on remplacer les proxies?
Exemple pour get:
var d2 = { "a": 1, "b": 2 }
function p(x) {
return x * 10
}
d2.c = 3
for(var z in d2) {
document.write(z + ":" )
document.write(p(d2[z]))
}
L'écriture est en fait plus simple sans proxy. Mais ils ne sont évidemment pas fait pour des opérations aussi élémentaires.
Quel est le réel intérêt?
Probablement pas d'intérêt tant que l'on exécute des opérations simples. C'est surtout un outil de clarification du code, qui permet de mettre en second plan les opérations utilitaires comme les contrôles sur les arguments, pour se concentrer sur la logique de l'application. On pourrait en dire autant de la programmation objet si ce n'est que celle-ci facilite aussi la réutilisabilité.
Dans une moindre mesure, le code développé pour les proxies est cependant réutilisable puisque dans la plupart des cas, le proxy applique une fonction à un objet. Cette fonction peut se réutiliser dans de nouveaux proxies.
Quelques exemples d'applications des proxies
- Sécurité. On place des contrôles de validité sur les paramètres d'une fonction, les valeurs d'un objet.
- Persistence des données. On ajoute dans un proxy une fonction de sauvegarde à chaque objet, qui se déclenche quand on modifie son contenu.
- Statistiques. On associe des décomptes et des calculs statistiques aux objets lorsque ceux-ci sont sont concernés par l'action des utilisateurs d'une application.
- Programmation contextuelle. Pour des contextes de traitement différents, on utilise des proxies différents. On a par exemple, un contexte de déboguage et un contexte de production. Pour le déboguage, les proxies présentent les valeurs des variables et permettent de modifier directement le contenu des objets.
- Mediator pattern (modèle médiateur). Les proxies sont les médiateurs entre objets qui interagissent par leur intermédiaire. On n'a pas besoin de définir des relations entre tel et tel objet.
- Accès conditionnel. Quand on accède à un objet par un proxy, il est possible d'interdire l'accès à tous les utilisateurs ou à un groupe, à tout moment, par une commande directe ou automatiquement en fonction de certaines conditions.