--- title: Proxy slug: Web/JavaScript/Reference/Objets_globaux/Proxy tags: - ECMAScript 2015 - JavaScript - Proxy - Reference translation_of: Web/JavaScript/Reference/Global_Objects/Proxy ---
L'objet Proxy est utilisé afin de définir un comportement sur mesure pour certaines opérations fondamentales (par exemple, l'accès aux propriétés, les affectations, les énumérations, les appels de fonctions, etc.).
var p = new Proxy(cible, gestionnaire);
cible
Proxy
.gestionnaire
Proxy
révocable.L'objet utilisé comme gestionnaire regroupe les différentes fonctions « trappes » pour le Proxy
.
Dans ce court exemple, on renvoie le nombre 37
comme valeur par défaut lorsque la propriété nommée n'est pas présente dans l'objet. Pour cela, on utilise le gestionnaire correspondant à {{jsxref("Objets_globaux/Proxy/handler/get","get")}}.
var handler = { get: function(obj, prop){ return prop in obj? obj[prop] : 37; } }; var p = new Proxy({}, handler); p.a = 1; p.b = undefined; console.log(p.a, p.b); // 1, undefined console.log('c' in p, p.c); // false, 37
Dans cet exemple, le proxy transfère toutes les opérations qui sont appliquées à l'objet cible.
var cible = {}; var p = new Proxy(cible, {}); p.a = 37; // L'opération est transmise à la cible par le proxy console.log(cible.a); // 37. L'opération a bien été transmise
En utilisant un Proxy
, il devient simple de valider les valeurs passées à un objet. Dans cet exemple, on utilise le gestionnaire correspondant à {{jsxref("Objets_globaux/Proxy/handler/set","set")}}.
let validateur = { set: function(obj, prop, valeur) { if (prop === 'âge') { if (!Number.isInteger(valeur)) { throw new TypeError('Cet âge n\'est pas un entier.'); } if (valeur > 200) { throw new RangeError('Cet âge semble invalide.'); } } // Le comportement par défaut : enregistrer la valeur obj[prop] = valeur; // On indique le succès de l'opération return true; } }; let personne = new Proxy({}, validateur); personne.âge = 100; console.log(personne.âge); // 100 personne.âge = 'jeune'; // lève une exception personne.âge = 300; // lève une exception
En utilisant une fonction proxy, on peut étendre un constructeur avec un nouveau constructeur. Dans cet exemple, on utilise les gestionnaires correspondants à {{jsxref("Objets_globaux/Proxy/handler/construct","construct")}} et {{jsxref("Objets_globaux/Proxy/handler/apply","apply")}}.
function étendre(sup,base) { var descripteur = Object.getOwnPropertyDescriptor( base.prototype, "constructor" ); base.prototype = Object.create(sup.prototype); var gestionnaire = { construct: function(cible, args) { var obj = Object.create(base.prototype); this.apply(cible,obj,args); return obj; }, apply: function(cible, that, args) { sup.apply(that,args); base.apply(that,args); } }; var proxy = new Proxy(base,gestionnaire); descripteur.value = proxy; Object.defineProperty(base.prototype, "constructor", descripteur); return proxy; } var Personne = function(nom){ this.nom = nom; }; var Garçon = étendre(Personne, function(nom, âge) { this.âge = âge; }); Garçon.prototype.genre = "M"; var Pierre = new Garçon("Pierre", 13); console.log(Pierre.genre); // "M" console.log(Pierre.nom); // "Pierre" console.log(Pierre.âge); // 13
Parfois, on veut passer un attribut ou un nom de classe entre deux éléments différents. Dans cet exemple, on utilise le gestionnaire lié à {{jsxref("Objets_globaux/Proxy/handler/set","set")}}.
let vue = new Proxy({ selected: null }, { set: function(obj, prop, nouvelleValeur) { let ancienneValeur = obj[prop]; if (prop === 'selected') { if (ancienneValeur) { ancienneValeur.setAttribute('aria-selected', 'false'); } if (nouvelleValeur) { nouvelleValeur.setAttribute('aria-selected', 'true'); } } // Le comportement par défaut : enregistrer la valeur obj[prop] = nouvelleValeur; // On indique le succès de l'opération return true; } }); let i1 = vue.selected = document.getElementById('item-1'); console.log(i1.getAttribute('aria-selected')); // 'true' let i2 = vue.selected = document.getElementById('item-2'); console.log(i1.getAttribute('aria-selected')); // 'false' console.log(i2.getAttribute('aria-selected')); // 'true'
Dans l'exemple qui suit, le proxy produits
évalue la valeur passée et la convertit en tableau si besoin. L'objet supporte également la propriété supplémentaire dernierNavigateur
à la fois comme accesseur et mutateur.
let produits = new Proxy({ navigateurs: ['Internet Explorer', 'Netscape'] }, { get: function(obj, prop) { // Une propriété supplémentaire if (prop === 'dernierNavigateur') { return obj.navigateurs[obj.navigateurs.length - 1]; } // Le comportement par défaut : renvoyer la valeur return obj[prop]; }, set: function(obj, prop, valeur) { // Une propriété supplémentaire if (prop === 'dernierNavigateur') { obj.navigateurs.push(valeur); return true; } // on convertit la valeur si ce n'est pas un tableau if (typeof valeur === 'string') { valeur = [valeur]; } // Le comportement par défaut : enregistrer la valeur obj[prop] = valeur; // On indique le succès de l'opération return true; } }); console.log(produits.navigateurs); // ['Internet Explorer', 'Netscape'] produits.navigateurs = 'Firefox'; // on passe une chaîne console.log(produits.navigateurs); // ['Firefox'] <- pas de problème, elle est convertie en tableau produits.dernierNavigateur = 'Chrome'; console.log(produits.navigateurs); // ['Firefox', 'Chrome'] console.log(produits.dernierNavigateur); // 'Chrome'
Dans cet exemple, ce proxy étend le tableau avec des fonctionnalités supplémentaires. Ici, on définit des propriétés sans utiliser {{jsxref("Objets_globaux/Object/defineProperties","Object.defineProperties")}}. Cet exemple pourrait être adapté pour trouver la ligne d'un tableau à partir d'une de ces cellules (la cible serait alors table.rows
).
let produits = new Proxy([ { nom: 'Firefox', type: 'navigateur' }, { nom: 'SeaMonkey', type: 'navigateur' }, { nom: 'Thunderbird', type: 'client mail' } ], { get: function(obj, prop) { // Le comportement par défaut : on renvoie la valeur // prop est généralement un entier if (prop in obj) { return obj[prop]; } // On obtient le nombre de produits // un alias pour products.length if (prop === 'nombre') { return obj.length; } let résultat, types = {}; for (let produit of obj) { if (produit.nom === prop) { résultat = produit; } if (types[produit.type]) { types[produit.type].push(produit); } else { types[produit.type] = [produit]; } } // Obtenir un produit grâce à un nom if (résultat) { return résultat; } // Obtenir un produit par type if (prop in types) { return types[prop]; } // Obtenir les types de produits if (prop === 'types') { return Object.keys(types); } return undefined; } }); console.log(produits[0]); // { nom: 'Firefox', type: 'navigateur' } console.log(produits['Firefox']); // { nom: 'Firefox', type: 'navigateur' } console.log(produits['Chrome']); // undefined console.log(produits.navigateur); // [{ nom: 'Firefox', type: 'navigateur' }, { nom: 'SeaMonkey', type: 'navigateur' }] console.log(produits.types); // ['navigateur', 'client mail'] console.log(produits.nombre); // 3
Pour illustrer l'ensemble des trappes, on tente de « proxifier » un objet non natif : l'objet global docCookies
créé grâce à cet exemple.
/* var docCookies = ... définir l'objet "docCookies" grâce à https://developer.mozilla.org/en-US/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support */ var docCookies = new Proxy(docCookies, { "get": function (oTarget, sKey) { return oTarget[sKey] || oTarget.getItem(sKey) || undefined; }, "set": function (oTarget, sKey, vValue) { if (sKey in oTarget) { return false; } return oTarget.setItem(sKey, vValue); }, "deleteProperty": function (oTarget, sKey) { if (sKey in oTarget) { return false; } return oTarget.removeItem(sKey); }, "enumerate": function (oTarget, sKey) { return oTarget.keys(); }, "ownKeys": function (oTarget, sKey) { return oTarget.keys(); }, "has": function (oTarget, sKey) { return sKey in oTarget || oTarget.hasItem(sKey); }, "defineProperty": function (oTarget, sKey, oDesc) { if (oDesc && "value" in oDesc) { oTarget.setItem(sKey, oDesc.value); } return oTarget; }, "getOwnPropertyDescriptor": function (oTarget, sKey) { var vValue = oTarget.getItem(sKey); return vValue ? { "value": vValue, "writable": true, "enumerable": true, "configurable": false } : undefined; }, }); /* Cookies test */ console.log(docCookies.mon_cookie1 = "Première valeur"); console.log(docCookies.getItem("mon_cookie1")); docCookies.setItem("mon_cookie1", "Valeur modifiée"); console.log(docCookies.mon_cookie1);
Spécification | État | Commentaires |
---|---|---|
{{SpecName('ES2015', '#sec-proxy-objects', 'Proxy')}} | {{Spec2('ES2015')}} | Définition initiale. |
{{SpecName('ES2016', '#sec-proxy-objects', 'Proxy')}} | {{Spec2('ES2016')}} | |
{{SpecName('ES2017', '#sec-proxy-objects', 'Proxy')}} | {{Spec2('ES2017')}} | |
{{SpecName('ESDraft', '#sec-proxy-objects', 'Proxy')}} | {{Spec2('ESDraft')}} |
{{Compat("javascript.builtins.Proxy", 2)}}
Certains composants de cette page (texte, exemples) ont été copiés ou adaptés du Wiki ECMAScript dont le contenu est sous licence CC 2.0 BY-NC-SA.