Proxy JavaScript
Qu'est-ce qu'un Proxy ?
Un Proxy est un objet JavaScript qui peut envelopper d'autres objets .
Un Proxy vous permet de contrôler les opérations sur d'autres objets.
Un Proxy peut intercepter et piéger du code lorsque quelqu'un :
- Lit une propriété (get)
- Définit une propriété (set)
- Supprime une propriété (deleteProperty)
- Vérifie si une propriété existe (has)
- Appelle une fonction (apply)
- Construit un objet (construct)
Remarque
Un Proxy vous permet de exécuter votre propre code lorsque quelqu'un interagit avec un objet.
Un Proxy peut être un intermédiaire entre votre code et un objet JavaScript.
Syntaxe du Proxy
const proxy = new Proxy(target, handler); - target - l'objet ou la fonction d'origine
- handler - un objet avec des méthodes de piège
Exemple
const myObject = {name: "Jan"};
const proxy = new Proxy(myObject, {
get(target, prop) {
return target[prop];
}
});
Journalisation du Proxy
Un exemple typique de Proxy est la journalisation des modifications d'objet.
Ci-dessous se trouve une démo qui :
- Enveloppe un objet dans un Proxy
- Journalise chaque fois qu'une propriété est lue ou écrite
- Journalise chaque opération get et set en temps réel
Exemple
Journalisez toutes les modifications des valeurs de propriété :
// Créer un objet
const user = { name: "Jan", age: 40 };
// Créer un Proxy
const proxy = new Proxy(user, {
get(target, property) {
log("Lecture : " + property);
return target[property];
},
set(target, property, value) {
log("Définition : " + property);
return target[property];
}
});
proxy.name = "John";
proxy.age = 42;
let text1 = proxy.name;
let text2 = proxy.age
Proxy avec Reflect (le plus courant)
Ci-dessous se trouve une démo qui :
- Enveloppe un objet dans un Proxy
- Journalise chaque fois qu'une propriété est lue ou écrite
- Utilise Reflect.get() et Reflect.set() à l'intérieur des gestionnaires de Proxy
- Le piège get utilise Reflect.get(target, property, receiver)
- Le piège set utilise Reflect.set(target, property, value, receiver)
- Reflect fait en sorte que le comportement du Proxy corresponde au comportement normal de l'objet
Exemple
Journalisez toutes les modifications des valeurs de propriété :
// Créer un objet
const user = { name: "Jan", age: 40 };
// Créer un Proxy
const proxy = new Proxy(user, {
get(target, property) {
log("Lecture : " + property); // transfert sécurisé
return Reflect.get(target, property);
},
set(target, property, value) {
log("Définition : " + property); // transfert sécurisé
return Reflect.set(target, property, value);
}
});
proxy.name = "John";
proxy.age = 42;
let text1 = proxy.name;
let text2 = proxy.age
Flux de Proxy avec Reflect
Le flux ci-dessous est l'essence de la métaprogrammation JavaScript avec Proxy + Reflect.
Explication du flux :
- Votre code interagit avec un objet
- Le Proxy intercepte l'opération
- Votre code de piège décide quoi faire
- Reflect transfère l'opération en toute sécurité
- L'objet cible reçoit l'action réelle
Pourquoi des Proxies ?
Les Proxies vous permettent de :
- Ajouter de la journalisation
- Valider les modifications
- Générer automatiquement des propriétés
- Protéger des données sensibles
- Créer des objets virtuels ou calculés
- Intercepter des appels de fonction
- Créer des systèmes réactifs (comme Vue.js)
Validation du Proxy
Exemple
// Créer un objet
const user = { name: "Jan", age: 40 };
// Créer un Proxy
const proxy = new Proxy(user, {
set(target, prop, value) {
if (prop === "age" && value < 0) {
text = "L'âge ne peut pas être négatif !";
document.getElementById("demo").innerHTML = text;
}
return Reflect.set(target, prop, value);
}
});
proxy.age = 45; // OK
proxy.age = -5; // Erreur
Propriétés Virtuelles
Exemple
// Créer un objet
const person = {
first: "John",
last: "Doe"
};
// Créer un Proxy
const virtual = new Proxy(person, {
get(target, prop) {
if (prop === "fullName") {
return target.first + " " + target.last;
}
return Reflect.get(target, prop);
}
});
let text = virtual.fullName; // "John Doe" Fonctions Dynamiques
Exemple
const fn = new Function("a", "b", "return a + b");
let result = fn(3, 2); Métaprogrammation
La métaprogrammation permet à JavaScript :
- D'intercepter le comportement
- De modifier le comportement
- De définir un nouveau comportement
- De générer un comportement dynamiquement
Elle donne aux développeurs un contrôle profond sur le fonctionnement interne du langage.
Pièges du Proxy
Un piège est une fonction à l'intérieur d'un gestionnaire de Proxy.
Il s'exécute chaque fois qu'une opération spécifique est effectuée sur le Proxy.
Ci-dessous se trouve une explication complète et précise de chaque piège Proxy JavaScript, ce qui les déclenche, leurs paramètres et ce qu'ils sont censés retourner.
| Nom du Piège | Déclenché lorsque |
|---|---|
| get | Une propriété est lue |
| set | Une propriété est changée |
| has | Utilisation de l' opérateur in |
| deleteProperty | Une propriété est supprimée |
| apply | Une fonction est appelée |
| construct | Un objet est créé (avec new) |
| getOwnPropertyDescriptor | Un descripteur de propriété est récupéré |
| defineProperty | Une propriété est définie |
| getPrototypeOf | Un prototype est récupéré |
| setPrototypeOf | Un prototype est défini |
| isExtensible | L'extensibilité est vérifiée |
| preventExtensions | L'extensibilité est empêchée |
| ownKeys | Les propriétés sont listées |
Remarque
La liste ci-dessus est précise pour 2025 et inclut tous les 13 pièges Proxy définis dans ECMAScript.
Chaque gestionnaire de piège est décrit ci-dessous.
handler.get()
Déclenché lorsqu'une propriété est lue :
get(obj, prop, receiver) {
return Reflect.get(obj, prop, receiver);
}
Déclenché par :
proxy.property
proxy["property"]
object.property()
Paramètres
- obj - l'objet cible
- prop - la propriété étant accédée
- receiver - la valeur this pour les getters (généralement le proxy lui-même)
Doit retourner
- La valeur de la propriété
handler.set()
Déclenché lorsqu'une propriété est changée :
set(obj, prop, value, receiver) {
return Reflect.set(obj, prop, value, receiver);
} Déclenché par :
proxy.property = value
proxy["property"] = value Paramètres
- obj - l'objet cible
- prop - la propriété étant accédée
- value - la nouvelle valeur de la propriété
- receiver - la valeur this pour les setters (généralement le proxy lui-même)
Doit retourner
- true - si l'assignation a réussi
- false - pour indiquer un échec
(Le lancer est également autorisé.)
handler.has()
Intercepte l' opérateur in .
has(obj, prop) {
return Reflect.has(obj, prop);
}
Déclenché par :
"property" in proxy
Paramètres
- obj - l'objet cible
- prop - la propriété étant accédée
- receiver - la valeur de this pour les setters (généralement le proxy lui-même)
Doit retourner
- true
- false
handler.deleteProperty()
Intercepte l' opérateur delete .
deleteProperty(obj, prop) {
return Reflect.deleteProperty(obj, prop);
}
Déclenché par :
delete proxy.property
Paramètres
- obj - l'objet cible
- prop - la propriété à supprimer
Doit retourner
- true - pour le succès
- false - pour l'échec
handler.apply()
Déclenché lorsqu'une fonction est appelée :
apply(func, thisArg, args) {
return Reflect.apply(func, this, args);
}
Déclenché par :
proxy()
proxy.call()
proxy.apply()
Paramètres
- func - l'objet appelable (fonction)
- this - l'argument this pour l'appel
- args - le tableau des arguments de la fonction
Doit retourner
- La valeur de retour de la fonction
handler.construct
Intercepte l' opérateur new .
construct(obj, args, newTarget) {
return Reflect.construct(obj, args, newTarget);
}
Déclenché par :
new proxy()
Paramètres
- obj - l'objet constructeur
- args - le tableau des arguments passés
- newTarget - le constructeur
Doit retourner
- Un objet (la nouvelle instance)
Le piège construct ne s'exécute que lorsque vous utilisez new .
const obj = {} // Pas de piège
Object.create() // Pas de piège
class User {};
new User(); // Pas de piège
handler.getOwnPropertyDescriptor()
Intercepte la récupération du descripteur de propriété.
getOwnPropertyDescriptor(obj, prop) {
return Reflect.getOwnPropertyDescriptor(obj, prop);
}
Déclenché par :
Object.getOwnPropertyDescriptor(obj, prop) Paramètres
- obj - l'objet cible
- prop - la propriété à décrire
Doit retourner
- Un descripteur de propriété
- undefined
handler.defineProperty()
Intercepte Object.defineProperty().
defineProperty(obj, prop, descriptor) {
return Reflect.defineProperty(obj, prop, descriptor);
}
Déclenché par :
Object.defineProperty()
Paramètres
- obj - l'objet cible
- prop - la propriété à décrire
- descriptor - le descripteur de propriété
Doit retourner
- true - si succès
- false - si non
handler.getPrototypeOf()
Intercepte la recherche de prototype.
getPrototypeOf(obj) {
return Reflect.getPrototypeOf(obj);
}
Déclenché par :
Object.getPrototypeOf()
Paramètres
- obj - l'objet cible
Doit retourner
- Un objet
- null
handler.setPrototypeOf()
Intercepte la définition du prototype.
setPrototypeOf(obj, prototype) {
return Reflect.setPrototypeOf(obj, prototype);
}
Déclenché par :
Object.setPrototypeOf()
Paramètres
- obj - l'objet cible
- prototype - le nouveau prototype ou null
Doit retourner
- Un objet
- null
handler.isExtensible()
Intercepte la vérification si un objet est extensible.
isExtensible(obj) {
return Reflect.isExtensible(obj);
}
Déclenché par :
Object.isExtensible() Paramètres
- obj - l'objet cible
Doit retourner
- true - pour oui
- false - pour non
handler.preventExtensions()
Intercepte la création d'un objet non extensible.
preventExtensions(obj) {
return Reflect.preventExtensions(obj);
}
Déclenché par :
Object.preventExtensions() Paramètres
- obj - l'objet cible
Doit retourner
- true - pour succès
- false - pour échec
handler.ownKeys()
Intercepte les opérations de liste des clés (noms de propriété ou symboles).
ownKeys(obj) {
return Reflect.ownKeys(obj);
}
Déclenché par :
Object.keys()
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Paramètres
- obj - l'objet cible
Doit retourner
- tableau de clés sans doublons
Méthodes de Réflexion des Pièges de Proxy
Un piège de Proxy représente l'une des opérations internes de JavaScript :
- [[Construct]]
- [[Call]]
- [[Get]]
- [[Set]]
- [[HasProperty]]
- [[Delete]]
- [[DefineProperty]]
- [[GetOwnProperty]]
- [[OwnPropertyKeys]]
- [[GetPrototypeOf]]
- [[SetPrototypeOf]]
- [[PreventExtensions]]
- [[IsExtensible]]
Ces opérations internes sont ce que JavaScript utilise (à l'intérieur du moteur) lorsque vous accédez ou modifiez des objets.
Lorsqu'un Proxy intercepte l'une de ces opérations, elles doivent être transférées correctement :
get(target, property, receiver) {
return Reflect.get(target, property, receiver);
} Reflect est utilisé car les méthodes Reflect sont des miroirs 1-à-1 des opérations internes.
- Elles produisent des valeurs de retour correctes (true/false/descripteur)
- Elles évitent de lancer des erreurs qui briseraient les règles du Proxy
- Elles font en sorte que le Proxy se comporte comme des objets JavaScript normaux (sauf modification)
Remarque
C'est pourquoi chaque piège de Proxy a une méthode Reflect avec le même nom et la même signature.
Les Internes de JavaScript Devenus Reflect
Avant ES6 (2015) , de nombreuses opérations fondamentales n'existaient pas en tant que fonctions :
| Opération | Avant E6 | Problème |
|---|---|---|
| obtenir une propriété | obj[prop] | Non appelable en tant que fonction |
| définir une propriété | obj[prop] = value | Non appelable en tant que fonction |
| supprimer une propriété | delete obj[prop] | Opérateur, pas une fonction |
| vérifier une propriété | "prop" in obj | Opérateur, pas une fonction |
| construire (new) | new Foo() | Non appelable de manière générique |
| obtenir des prototypes | Object.getPrototypeOf(obj) | Lance une erreur sur des non-objets |
| définir une propriété | Object.defineProperty() | Retourne un objet au lieu d'un booléen |
| propres clés | Object.keys() | Incomplet (manque des symboles) |
Celles-ci étaient incomplètes et incohérentes pour le transfert de Proxy.
Donc ES6 (2015) a ajouté Reflect :
| Opération Interne | Méthode Reflect |
|---|---|
| [[Construct]] | Reflect.construct() |
| [[Call]] | Reflect.apply() |
| [[Get]] | Reflect.get() |
| [[Set]] | Reflect.set() |
| [[HasProperty]] | Reflect.has() |
| [[Delete]] | Reflect.deleteProperty() |
| [[DefineProperty]] | Reflect.defineProperty() |
| [[GetOwnProperty]] | Reflect.getOwnProperty() |
| [[OwnPropertyKeys]] | Reflect.ownPropertyKeys() |
| [[IsExtensible]] | Reflect.isExtensible() |
| [[PreventExtensions]] | Reflect.preventExtensions() |
| [[GetPrototypeOf]] | Reflect.getPrototypeOf() |
| [[SetPrototypeOf]] | Reflect.setPrototypeOf() |