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
Essayez-le vous-même »

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
Essayez-le vous-même »

Flux de Proxy avec Reflect

Le flux ci-dessous est l'essence de la métaprogrammation JavaScript avec Proxy + Reflect.

Proxy

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
Essayez-le vous-même »

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"
Essayez-le vous-même »

Fonctions Dynamiques

Exemple

const fn = new Function("a", "b", "return a + b");

let result = fn(3, 2);
Essayez-le vous-même »

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