--- title: Proxy slug: Web/JavaScript/Reference/Global_Objects/Proxy tags: - ECMAScript 2015 - JavaScript - Proxy - Reference translation_of: Web/JavaScript/Reference/Global_Objects/Proxy original_slug: Web/JavaScript/Reference/Objets_globaux/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);
cibleProxy.gestionnaireProxy 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.