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.).

Terminologie

gestionnaire (handler)
Un objet qui contient les trappes qui intercepteront les opérations.
trappes
Les méthodes qui fournissent l'accès aux propriétés. Ce concept est analogue aux trappes utilisées dans les systèmes d'exploitations.
cible
L'objet virtualisé par le proxy. Il est souvent utilisé comme objet de stockage. Les invariants (c'est-à-dire les éléments de sémantique qui restent inchangés) relatifs à la non-extensibilité et au caractère non-configurable des propriétés sont vérifiés par rapport à la cible.

Syntaxe

var p = new Proxy(cible, gestionnaire);

Paramètres

cible
Une cible (qui peut être n'importe quel objet, un tableau, une fonction, ou même un autre proxy) qu'on souhaite envelopper dans un Proxy.
gestionnaire
Un objet dont les propriétés sont des fonctions qui définissent le comportement du proxy lorsqu'on utilise une opération sur celui-ci.

Méthodes

Proxy.revocable()
Permet de créer un objet Proxy révocable.

Méthodes pour le gestionnaire

L'objet utilisé comme gestionnaire regroupe les différentes fonctions « trappes » pour le Proxy.

Exemples

Exemple simple

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.

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

Proxy « invisible »

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

Validation

En utilisant un Proxy, il devient simple de valider les valeurs passées à un objet. Dans cet exemple, on utilise le gestionnaire correspondant à 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

Étendre un constructeur

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.

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);
  descriptor.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.sexe = "M";

var Pierre = new Garçon("Pierre", 13);
console.log(Pierre.sexe);  // "M"
console.log(Pierre.nom);  // "Pierre"
console.log(Pierre.âge);  // 13

Manipuler les nœuds DOM

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é à 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'

Corriger une valeur et ajouter une propriété supplémentaire

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'

Trouver un élément dans un tableau grâce à sa propriété

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).

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

Un exemple avec toutes les trappes

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écifications

Spécification État Commentaires
ECMAScript 2015 (6th Edition, ECMA-262)
La définition de 'Proxy' dans cette spécification.
Standard Définition initiale.
ECMAScript 2016 (ECMA-262)
La définition de 'Proxy' dans cette spécification.
Standard  
ECMAScript 2017 (ECMA-262)
La définition de 'Proxy' dans cette spécification.
Standard  
ECMAScript Latest Draft (ECMA-262)
La définition de 'Proxy' dans cette spécification.
Projet  

Compatibilité des navigateurs

Update compatibility data on GitHub
OrdinateurMobileServeur
ChromeEdgeFirefoxInternet ExplorerOperaSafariWebview AndroidChrome pour AndroidEdge MobileFirefox pour AndroidOpera pour AndroidSafari pour iOSSamsung InternetNode.js
Support simpleChrome Support complet 49Edge Support complet 12Firefox Support complet 18IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
revocableChrome Support complet OuiEdge Support complet OuiFirefox Support complet 34IE Aucun support NonOpera Support complet OuiSafari Support complet 10WebView Android Support complet OuiChrome Android Support complet OuiEdge Mobile Support complet OuiFirefox Android Support complet 34Opera Android Support complet OuiSafari iOS Support complet 10Samsung Internet Android Support complet Ouinodejs Support complet 6.0.0
handler.applyChrome Support complet 49Edge Support complet 12Firefox Support complet 18IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.constructChrome Support complet 49Edge Support complet 12Firefox Support complet 18IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.definePropertyChrome Support complet 49Edge Support complet 12Firefox Support complet 18IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.deletePropertyChrome Support complet 49Edge Support complet 12Firefox Support complet 18IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.enumerate
ObsolèteNon-standard
Chrome Aucun support NonEdge Aucun support NonFirefox Aucun support 37 — 47IE Aucun support NonOpera Aucun support NonSafari Aucun support NonWebView Android Aucun support NonChrome Android Aucun support NonEdge Mobile Aucun support NonFirefox Android Aucun support 37 — 47Opera Android Aucun support NonSafari iOS Aucun support NonSamsung Internet Android Aucun support Nonnodejs Aucun support Non
handler.getChrome Support complet 49Edge Support complet 12Firefox Support complet 18IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.getOwnPropertyDescriptorChrome Support complet 49Edge Support complet 12Firefox Support complet 18IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.getPrototypeOfChrome Aucun support NonEdge Aucun support NonFirefox Support complet 49IE Aucun support NonOpera Aucun support NonSafari Aucun support NonWebView Android Aucun support NonChrome Android Aucun support NonEdge Mobile Aucun support NonFirefox Android Support complet 49Opera Android Aucun support NonSafari iOS Aucun support NonSamsung Internet Android Aucun support Nonnodejs Support complet 6.0.0
handler.hasChrome Support complet 49Edge Support complet 12Firefox Support complet 18IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.isExtensibleChrome ? Edge ? Firefox Support complet 31IE Aucun support NonOpera ? Safari ? WebView Android ? Chrome Android ? Edge Mobile ? Firefox Android Support complet 31Opera Android ? Safari iOS ? Samsung Internet Android ? nodejs Support complet 6.0.0
handler.ownKeysChrome Support complet 49Edge Support complet 12Firefox Support complet 18
Notes
Support complet 18
Notes
Notes In Firefox 42, the implementation got updated to reflect the final ES2015 specification: The result is now checked if it is an array and if the array elements are either of type string or of type symbol. Enumerating duplicate own property names is not a failure anymore.
IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18
Notes
Support complet 18
Notes
Notes In Firefox 42, the implementation got updated to reflect the final ES2015 specification: The result is now checked if it is an array and if the array elements are either of type string or of type symbol. Enumerating duplicate own property names is not a failure anymore.
Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.preventExtensionsChrome Support complet 49Edge Support complet 12Firefox Support complet 22IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 22Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.setChrome Support complet 49Edge Support complet 12Firefox Support complet 18IE Aucun support NonOpera Support complet 36Safari Support complet 10WebView Android Support complet 49Chrome Android Support complet 49Edge Mobile Support complet OuiFirefox Android Support complet 18Opera Android Support complet 36Safari iOS Support complet 10Samsung Internet Android Support complet 5.0nodejs Support complet 6.0.0
handler.setPrototypeOfChrome ? Edge ? Firefox Support complet 49IE Aucun support NonOpera ? Safari ? WebView Android ? Chrome Android ? Edge Mobile ? Firefox Android Support complet 49Opera Android ? Safari iOS ? Samsung Internet Android ? nodejs Support complet 6.0.0

Légende

Support complet  
Support complet
Aucun support  
Aucun support
Compatibilité inconnue  
Compatibilité inconnue
Fonctionnalité non-standard. Celle-ci peut être incorrectement supportée par les autres navigateurs.
Fonctionnalité non-standard. Celle-ci peut être incorrectement supportée par les autres navigateurs.
Obsolète. Les nouveaux sites web ne doivent pas utiliser cette fonctionnalité.
Obsolète. Les nouveaux sites web ne doivent pas utiliser cette fonctionnalité.
Voir les notes d'implémentation.
Voir les notes d'implémentation.

Voir aussi

Notes de licence

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.

Étiquettes et contributeurs liés au document

Contributeurs à cette page : SphinxKnight, Watilin, lotfire24, noopole, sd65, jmpp, jessmania
Dernière mise à jour par : SphinxKnight,