HTML: Markup language
CSS: Styling language
JavaScript: Scripting language
Web APIs: Programming interfaces
All web technology
Learn web development
Discover our tools
Get to know MDN better
Cette page a été traduite à partir de l'anglais par la communauté. Vous pouvez contribuer en rejoignant la communauté francophone sur MDN Web Docs.
View in English Always switch to English
Cette fonctionnalité est bien établie et fonctionne sur de nombreux appareils et versions de navigateurs. Elle est disponible sur tous les navigateurs depuis septembre 2016.
Un objet Proxy permet de créer un intermédiaire pour un autre objet et qui peut intercepter et redéfinir certaines opérations fondamentales pour lui.
Un objet Proxy permet de créer un objet qui peut être utilisé à la place de l'objet original en redéfinissant certaines opérations fondamentales comme l'accès, la modification et la définition de propriétés. Les objets Proxy sont généralement utilisés pour journaliser l'accès aux propriétés, valider, formater ou nettoyer des valeurs saisies, etc.
Proxy
La création d'un objet Proxy se fait avec deux paramètres :
cible
L'objet original devant lequel on veut placer un intermédiaire
gestionnaire
Un objet qui définit les opérations qui seront interceptées et comment celles-ci seront redéfinies.
Dans l'exemple qui suit, on a une cible simple avec deux propriétés et un gestionnaire encore plus simple, sans propriété.
const cible = { message1: "coucou", message2: "tout le monde", }; const gestionnaire1 = {}; const proxy1 = new Proxy(cible, gestionnaire1);
Le gestionnaire étant vide, le proxy se comporte à l'identique de la cible :
console.log(proxy1.message1); // coucou console.log(proxy1.message2); // tout le monde
Pour adapter le proxy, on définit des fonctions sur le gestionnaire :
const cible = { message1: "coucou", message2: "tout le monde", }; const gestionnaire2 = { get(cible, prop, recepteur) { return "le monde"; }, }; const proxy2 = new Proxy(cible, gestionnaire2);
Ici, on a fourni une implémentation du gestionnaire get(), qui intercepte les tentatives d'accès aux propriétés de la cible.
get()
Les fonctions d'un gestionnaire sont parfois appelées des trappes, car les appels originaux tombent dans ces trappes. Celle qui est utilisée dans gestionnaire2 redéfinit l'accès pour toutes les propriétés :
gestionnaire2
console.log(proxy2.message1); // le monde console.log(proxy2.message2); // le monde
Avec Reflect, on peut rediriger certains accesseurs vers leur comportement original et en redéfinir d'autres :
Reflect
const cible = { message1: "coucou", message2: "tout le monde", }; const gestionnaire3 = { get(cible, prop, recepteur) { if (prop === "message2") { return "le monde"; } return Reflect.get(...arguments); }, }; const proxy3 = new Proxy(cible, gestionnaire3); console.log(proxy3.message1); // coucou console.log(proxy3.message2); // le monde
Proxy()
Crée un nouvel objet Proxy.
Proxy.revocable()
Crée un objet Proxy révocable.
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 à get().
37
const handler = { get(obj, prop) { return prop in obj ? obj[prop] : 37; }, }; const 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.
const target = {}; const p = new Proxy(target, {}); p.a = 37; // L'opération est transmise à la cible par le proxy console.log(target.a); // 37 // L'opération a bien été transmise
On notera que bien que ceci fonctionne pour les objets JavaScript construits dans les scripts, ça ne fonctionne pas pour les objets natifs de l'environnement (comme les éléments du DOM dans un navigateur).
En utilisant un Proxy, on peut simplement valider les valeurs passées à un objet. Dans cet exemple, on utilise le gestionnaire correspondant à set().
set()
let validateur = { set: function (obj, prop, valeur) { if (prop === "age") { if (!Number.isInteger(valeur)) { throw new TypeError("Cet a 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; }, }; const personne = new Proxy({}, validateur); personne.age = 100; console.log(personne.age); // 100 personne.age = "jeune"; // lève une exception personne.age = 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 à construct() et apply().
construct()
apply()
function etendre(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 Garcon = etendre(Personne, function (nom, âge) { this.âge = âge; }); Garcon.prototype.genre = "M"; var Pierre = new Garcon("Pierre", 13); console.log(Pierre.genre); // "M" console.log(Pierre.nom); // "Pierre" console.log(Pierre.âge); // 13
Dans cet exemple, on utilise Proxy afin qu'un attribut alterne entre deux éléments différents : si on définit l'attribut sur un élément, il sera retiré de l'autre.
On crée un objet vue qui est un proxy pour l'objet avec une selected. Le gestionnaire du proxy définit la fonction set().
vue
selected
Lorsqu'on affecte un élément HTML à vue.selected, l'attribut 'aria-selected' de l'élément est placé à true. Si on affecte ensuite un autre élément à vue.selected, ce nouvel élément aura l'attribut 'aria-selected' défini à true et l'élément précédent verra son attribut 'aria-selected' automatiquement défini à false.
vue.selected
'aria-selected'
true
false
let vue = new Proxy( { selected: null, }, { set(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; }, }, ); const element1 = document.getElementById("elem-1"); const element2 = document.getElementById("elem-2"); // on sélectionne element1 vue.selected = element1; console.log(`element1 : ${element1.getAttribute("aria-selected")}`); // element1 : true // on sélectionne element2 et cela entraîne // la déselection automatique de element1 vue.selected = element2; console.log(`element1 : ${element1.getAttribute("aria-selected")}`); // element1 : false console.log(`element2 : ${element2.getAttribute("aria-selected")}`); // element2 : true
Dans l'exemple qui suit, le proxy produits évalue la valeur passée et la convertit en tableau si besoin. L'objet prend également en charge la propriété supplémentaire dernierNavigateur à la fois comme accesseur et mutateur.
produits
dernierNavigateur
let produits = new Proxy( { navigateurs: ["Internet Explorer", "Netscape"], }, { get(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(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 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).
Object.defineProperties()
table.rows
let produits = new Proxy( [ { nom: "Firefox", type: "navigateur" }, { nom: "SeaMonkey", type: "navigateur" }, { nom: "Thunderbird", type: "client mail" }, ], { get(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 resultat, types = {}; for (let produit of obj) { if (produit.nom === prop) { resultat = produit; } if (types[produit.type]) { types[produit.type].push(produit); } else { types[produit.type] = [produit]; } } // Obtenir un produit grâce à un nom if (resultat) { return resultat; } // 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.
docCookies
/* var docCookies = ... définir l'objet "docCookies" grâce à https://reference.codeproject.com/dom/document/cookie/simple_document.cookie_framework */ var docCookies = new Proxy(docCookies, { get(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); }, 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; }, }); /* Test */ console.log((docCookies.monCookie1 = "Première valeur")); console.log(docCookies.getItem("monCookie1")); docCookies.setItem("monCookie1", "Valeur modifiée"); console.log(docCookies.monCookie1);
Activez JavaScript pour afficher ce tableau de compatibilité des navigateurs.