Aperçu

Proxy et Reflect sont des API introduites par ES6 pour manipuler des objets.

Proxy peut intercepter les opérations sur l'objet cible, telles que la lecture et les appels de fonctions, puis effectuer le traitement des opérations. Il n'opère pas directement sur l'objet, mais, comme le modèle de proxy, il opère via un objet proxy de l'objet, et pendant ces opérations, des opérations supplémentaires nécessaires peuvent être ajoutées.

Reflect peut être utilisé pour obtenir le comportement de l'objet cible. Il est similaire à Object, mais plus lisible, offrant une manière plus élégante de manipuler des objets. Ses méthodes correspondent à Proxy.


Utilisation de base

Proxy

Un objet Proxy se compose de deux parties : target et handler. Lors de la création d'un objet instance via le constructeur Proxy, ces deux paramètres doivent être fournis. target est l'objet cible, et handler est un objet qui déclare le comportement spécifié pour proxifier target.

let target = { name: 'Tom', age: 24 } let handler = { get: function(target, key) { console.log('getting '+key); return target[key]; //target.key non }, défini: function(target, key, value) { console.log('définition '+key); target[key] = value; } } let proxy = new Proxy(target, handler) proxy.name //Exécute en fait handler.get proxy.age = 25 //Exécute en fait handler.set // récupération de name // définition de age // 25 //target peut être un objet vide let targetEpt = {} let proxyEpt = new Proxy(targetEpt, handler) //Appelez la méthode get. À ce moment, l'objet target est vide et n'a pas de propriété name. proxyEpt.name // récupération de name //Appelez la méthode set pour ajouter la propriété name à l'objet target. proxyEpt.name = 'Tom' // définition du nom // "Tom" //Appelez get à nouveau. À ce moment, la propriété name existe déjà. proxyEpt.name // récupération de name // "Tom" //Lors de la création d'une nouvelle instance via le constructeur, il effectue en réalité une copie superficielle de l'objet cible, donc l'objet cible et l'objet proxy vont mutuellement s' //affecter targetEpt // {name: "Tom"} //L'objet handler peut également être vide, ce qui équivaut à ne pas définir d'opérations d'interception et à accéder directement à l'objet cible. let targetEmpty = {} let proxyEmpty = new Proxy(targetEmpty,{}) proxyEmpty.name = "Tom" targetEmpty // {name: "Tom"}

Méthodes d'instance

get(target, propKey, receiver)

Utilisé pour l'opération de lecture de propKey sur l'objet cible.

let exam ={ name: "Tom", age: 24 } let proxy = new Proxy(exam, { get(target, propKey, receiver) { console.log('Récupération ' + propKey); return target[propKey]; } }) proxy.name // Récupération du nom // "Tom"

La méthode get() peut être héritée.

let proxy = new Proxy({}, { get(target, propKey, receiver) { //Implémenter la protection en lecture des propriétés privées if(propKey[0] === '_'){ throw new Erro(`Tentative invalide pour obtenir la propriété privée "${propKey}"`); } console.log('Récupération ' + propKey); return target[propKey]; } }); let obj = Object.create(proxy); obj.name // Récupération du nom
set(target, propKey, value, receiver)

Utilisé pour intercepter l'opération d'affectation de propKey sur l'objet cible. Si une propriété de l'objet cible lui-même est non accessible en écriture et non configurable, alors la méthode set n'aura aucun effet.

let validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('L'âge n'est pas un entier'); } if (value > 200) { throw new RangeError('L'âge semble invalide'); } } //Pour la propriété age et les autres propriétés qui remplissent les conditions, enregistrez-les directement. obj[prop] = value; } }; let proxy= new Proxy({}, validator) proxy.age = 100; proxy.age // 100 proxy.age = 'oppps' //Erreur signalée proxy.age = 300 //Erreur signalée

Le quatrième paramètre receiver représente l'objet où se situe le comportement de l'opération d'origine, généralement l'instance Proxy elle-même.

const handler = { set: function(obj, prop, value, receiver) { obj[prop] = receiver; } }; const proxy = new Proxy({}, handler); proxy.name= 'Tom'; proxy.name=== proxy // true const exam = {} Object.setPrototypeOf(exam, proxy) exam.name = "Tom" exam.name === exam // vrai

Remarque : en mode strict, si le proxy set ne renvoie pas true, une erreur sera signalée.

apply(target, ctx, args)

Utilisé pour intercepter les appels de fonction, les opérations call et reply. target représente l'objet cible, ctx représente le contexte de l'objet cible, et args représente le tableau d'arguments de l'objet cible.

function sub(a, b){ return a - b; } let handler = { apply: function(target, ctx, args){ console.log('handle apply'); return Reflect.apply(...arguments); } } let proxy = new Proxy(sub, handler) proxy(2, 1) // handle apply // 1
has(target, propKey)

Utilisé pour intercepter l'opération HasProperty, c'est-à-dire que lorsqu'on détermine si l'objet cible possède la propriété propKey, elle sera interceptée par cette méthode. Cette méthode ne détermine pas si une propriété est une propriété propre à un objet ou une propriété héritée.

let handler = { has: function(target, propKey){ console.log("handle has"); return propKey in target; } } let exam = {name: "Tom"} let proxy = new Proxy(exam, handler) 'name' in proxy // handle has // true

Remarque : Cette méthode n'intercepte pas les boucles for...in.

construct(target, args)

Utilisé pour intercepter la commande new. La valeur de retour doit être un objet.

let handler = { construct: function (target, args, newTarget) { console.log('handle construct') return Reflect.construct(target, args, newTarget) } } class Exam { constructor (name) { this.name = name } } let ExamProxy = new Proxy(Exam, handler) let proxyObj = new ExamProxy('Tom') console.log(proxyObj) // handle construct // exam {name: "Tom"}
deleteProperty(target, propKey)

Utilisé pour intercepter l'opération delete. Si cette méthode lève une erreur ou renvoie false, la propriété propKey ne peut pas être supprimée par la commande delete.

defineProperty(target, propKey, propDesc)

Utilisé pour intercepter Object.definePro. Si l'objet cible n'est pas extensible, l'ajout d'une propriété qui n'existe pas sur l'objet cible générera une erreur ; si une propriété est non inscriptible ou non configurable, ces propriétés ne peuvent pas être modifiées.

let handler = { defineProperty: function(target, propKey, propDesc){ console.log("handle defineProperty"); return true; } } let target = {} let proxy = new Proxy(target, handler) proxy.name = "Tom" // gérer defineProperty target // {name: "Tom"} //Lorsque defineProperty renvoie false, l'opération d'ajout de propriété est invalide. let handler1 = { defineProperty: function(target, propKey, propDesc){ console.log("handle defineProperty"); return false; } } let target1 = {} let proxy1 = new Proxy(target1, handler1) proxy1.name = "Jerry" target1 // {}

erty opération

getOwnPropertyDescriptor(target, propKey)

Utilisé pour intercepter Object.getOwnPropertyD(). La valeur de retour est un objet descripteur de propriété ou undefined.

let handler = { getOwnPropertyDescriptor: function(target, propKey){ return Object.getOwnPropertyDescriptor(target, propKey); } } let target = {name: "Tom"} let proxy = new Proxy(target, handler) Object.getOwnPropertyDescriptor(proxy, 'name') // {value: "Tom", writable: true, enumerable: true, configurable: // true}

ptor propriété

getPrototypeOf(target)

Principalement utilisé pour intercepter les opérations qui récupèrent le prototype de l'objet. Inclut les opérations suivantes :

- Object.prototype._proto_
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
let exam = {} let proxy = new Proxy({},{ getPrototypeOf: function(target){ return exam; } }) Object.getPrototypeOf(proxy) // {}

Remarque : la valeur de retour doit être un objet ou null, sinon une erreur sera signalée. De plus, si l'objet cible n'est pas extensible, la méthode getPrototypeOf doit retourner l'objet prototype de l'objet cible.

let proxy = new Proxy({},{ getPrototypeOf: function(target){ return true; } }) Object.getPrototypeOf(proxy) // TypeError: 'getPrototypeOf' sur le proxy : le piège n'a renvoyé ni objet // ni null
isExtensible(target)

Utilisé pour intercepter l'opération Object.isExtensible.

Cette méthode ne peut renvoyer qu'une valeur booléenne ; sinon, la valeur de retour sera automatiquement convertie en booléen.

let proxy = new Proxy({},{ isExtensible:function(target){ return true; } }) Object.isExtensible(proxy) // true

Remarque : sa valeur de retour doit être cohérente avec la propriété isExtensible de l'objet cible, sinon une erreur sera levée.

let proxy = new Proxy({},{ isExtensible:function(target){ return false; } }) Object.isExtensible(proxy) // TypeError: 'isExtensible' sur le proxy : le résultat du piège ne reflète pas // l'extensibilité de la cible du proxy (qui est 'true')
ownKeys(target)

Utilisé pour intercepter l'opération de lecture des propriétés propres d'un objet. Cela comprend principalement les opérations suivantes :

- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- or...in

Les membres du tableau renvoyés par la méthode ne peuvent être que des chaînes de caractères ou des valeurs Symbol, sinon une erreur sera signalée.

Si l'objet cible contient des propriétés non configurables, ces propriétés doivent être renvoyées dans le résultat, sinon une erreur sera signalée.

Si l'objet cible n'est pas extensible, il doit renvoyer toutes et uniquement toutes les propriétés contenues dans l'objet cible ; il ne peut pas inclure de propriétés inexistantes, sinon une erreur sera également signalée.

let proxy = new Proxy( { name: "Tom", age: 24 }, { ownKeys(target) { return ['name']; } }); Object.keys(proxy) //[ 'name' ]f Dans le résultat renvoyé, trois types de propriétés seront filtrés : //- Propriétés absentes de l'objet cible //- Propriétés dont les noms sont des valeurs Symbol //- Propriétés non énumérables let target = { name: "Tom", [Symbol.for('age')]: 24, }; //Ajouter une propriété non énumérable 'gender' Object.defineProperty(target, 'gender', { enumerable: false, configurable: true, writable: true, value: 'male' }); let handler = { ownKeys(target) { return ['name', 'parent', Symbol.for('age'), 'gender']; } }; let proxy = new Proxy(target, handler); Object.keys(proxy) // ['name']
preventExtensions(target)

Intercepter l'opération Object.preventExtensions.

Cette méthode doit renvoyer une valeur booléenne, sinon elle sera automatiquement convertie en booléen.

//Seulement lorsque l'objet cible n'est pas extensible (c'est-à-dire que Object.isExtensible(proxy) est false), //proxy.preventExtensions peut renvoyer true ; sinon, une erreur sera signalée var proxy = new Proxy({}, { preventExtensions: function(target) { return true; } }); //Parce que proxy.preventExtensions renvoie true, il renvoie aussi true ici, donc une erreur sera signalée Object.preventExtensions(proxy)Par// TypeError: 'preventExtensions' sur proxy: le piège a renvoyé truish mais // la cible du proxy est extensible //Solution var proxy = new Proxy({}, { preventExtensions: function(target) { //Appeler Object.preventExtensions avant de retourner Object.preventExtensions(target); return true; } }); Object.preventExtensions(proxy) // Proxy {}
setPrototypeOf

Principalement utilisé pour intercepter la méthode Object.setPrototypeOf.

La valeur de retour doit être un booléen, sinon elle sera automatiquement convertie en booléen.

Si l'objet cible n'est pas extensible, la méthode setPrototypeOf ne doit pas modifier le prototype de l'objet cible.

let proto = {} let proxy = new Proxy(function () {}, { setPrototypeOf: function(target, proto) { console.log("setPrototypeOf"); return true; } } ); Object.setPrototypeOf(proxy, proto); // setPrototypeOf
Proxy.revocable()

Utilisé pour renvoyer une instance Proxy révocable.

let {proxy, revoke} = Proxy.revocable({}, {}); proxy.name = "Tom"; revoke(); proxy.name // TypeError: Impossible d'effectuer 'get' sur un proxy qui a été révoqué

Reflect

En ES6, certaines méthodes d'Object qui appartiennent clairement aux internes du langage ont été déplacées vers l'objet Reflect (actuellement, certaines méthodes existent à la fois sur les objets Object et Reflect), et les nouvelles méthodes futures seront déployées uniquement sur l'objet Reflect.

L'objet Reflect a modifié les résultats de retour de certaines méthodes pour les rendre plus raisonnables.

L'objet Reflect utilise des fonctions pour implémenter les opérations impératives d'Object.

Méthodes statiques

Reflect.get(target, name, receiver)

Rechercher et renvoyer la propriété name de l'objet cible.

let exam = { name: "Tom", age: 24, get info(){ return this.name + this.age; } } Reflect.get(exam, 'name'); // "Tom" //Lorsque l'objet cible possède une méthode getter pour la propriété name, le this de la méthode getter sera lié à // receiver let receiver = { name: "Jerry", age: 20 } Reflect.get(exam, 'info', receiver); // Jerry20 //Lorsque name est une propriété qui n'existe pas sur l'objet cible, renvoie undefined Reflect.get(exam, 'birth'); // undefined //Lorsque target n'est pas un objet, une erreur sera signalée Reflect.get(1, 'name'); // TypeError
Reflect.set(target, name, value, receiver)

Définit la propriété name de target sur value. La valeur de retour est un booléen ; true indique un succès, false indique un échec. Lorsque target est un objet inexistant, une erreur sera signalée.

let exam = { name: "Tom", age: 24, set info(value){ return this.age = value; } } exam.age; // 24 Reflect.set(exam, 'age', 25); // true exam.age; // 25 //Lorsque la valeur est vide, la propriété name sera supprimée. Reflect.set(exam, 'age', ); // true exam.age; // undefined //Lorsque l'objet cible possède une méthode setter pour la propriété name, le this dans la méthode setter sera lié à // receiver, donc ce qui est réellement modifié est la propriété de receiver, let receiver = { age: 18 } Reflect.set(exam, 'info', 1, receiver); // true receiver.age; // 1 let receiver1 = { name: 'oppps' } Reflect.set(exam, 'info', 1, receiver1); receiver1.age; // 1
Reflect.has(obj, name)

C'est la forme fonctionnelle de l'opérateur name in obj, utilisée pour vérifier si la propriété name existe dans l'objet obj. La valeur de retour est boolean. Si obj n'est pas un objet, une TypeError sera signalée.

let exam = { name: "Tom", age: 24 } Reflect.has(exam, 'name'); // true
Reflect.deleteProperty(obj, property)

C'est la forme fonctionnelle de delete obj[property], utilisée pour supprimer la propriété property de l'objet obj. La valeur de retour est boolean. Si obj n'est pas un objet, une TypeError sera signalée.

let exam = { name: "Tom", age: 24 } Reflect.deleteProperty(exam , 'name'); // true exam // {age: 24} //Lorsque la propriété n'existe pas, il renvoie également true Reflect.deleteProperty(exam , 'name'); // vrai
Reflect.construct(obj, args)

Équivalent à new target(...args).

function exam(name){ this.name = name; } Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}
Reflect.getPrototypeOf(obj)

Utilisé pour lire la propriété _proto_ de obj. Lorsque obj n'est pas un objet, il ne convertit pas obj en objet comme le fait Object, mais signale une erreur à la place.

class Exam{} let obj = new Exam() Reflect.getPrototypeOf(obj) === Exam.prototype // true
Reflect.setPrototypeOf(obj, newProto)

Utilisé pour définir le prototype de l'objet cible.

let obj ={} Reflect.setPrototypeOf(obj, Array.prototype); // true
Reflect.apply(func, thisArg, args)

Équivalent à Function.prototype.apply.call(func, thisArg, args). func représente la fonction cible ; thisArg représente l'objet this lié à la fonction cible ; args représente la liste d'arguments transmise lorsque la fonction cible est appelée, qui peut être un tableau ou un objet de type tableau. Si la fonction cible ne peut pas être appelée, une TypeError est levée.

Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5
Reflect.defineProperty(target, propertyKey, attributes)

Utilisé pour définir des propriétés pour l'objet cible. Si target n'est pas un objet, une erreur est levée.

let myDate= {} Reflect.defineProperty(MyDate, 'now', { value: () => Date.now() }); // true const student = {}; Reflect.defineProperty(student, "name", {value: "Mike"}); // true student.name; // "Mike"
Reflect.getOwnPropertyDescriptor(target, propertyKey)

Utilisé pour obtenir l'objet descripteur de la propriété propertyKey de l'objet target. Lorsque target n'est pas un objet, une erreur est levée pour indiquer un paramètre invalide ; le non-objet n'est pas converti en objet.

var exam = {} Reflect.defineProperty(exam, 'name', { value: true, enumerable: false, }) Reflect.getOwnPropertyDescriptor(exam, 'name') // { configurable: false, enumerable: false, value: true, writable: // false} //Si la propriété propertyKey n'existe pas sur l'objet target, renvoie undefined. Reflect.getOwnPropertyDescriptor(exam, 'age') // undefined
Reflect.isExtensible(target)

Utilisé pour déterminer si l'objet target est extensible. La valeur de retour est un booléen. Si le paramètre target n'est pas un objet, une erreur est levée.

let exam = {} Reflect.isExtensible(exam) // true
Reflect.preventExtensions(target)

Utilisé pour rendre l'objet cible non extensible. Si le paramètre cible n'est pas un objet, une erreur est levée.

let exam = {} Reflect.preventExtensions(exam) // true
Reflect.ownKeys(target)

Utilisé pour retourner toutes les propriétés de l'objet cible, équivalent à la combinaison de Object.getOwnPropertyNames et Object.getOwnPropertySymbols.

var exam = { name: 1, [Symbol.for('age')]: 4 } Reflect.ownKeys(exam) // ["name", Symbol(age)]

Utilisation combinée

Les méthodes de l'objet Reflect correspondent un à un aux méthodes de l'objet Proxy. Par conséquent, les méthodes de l'objet Proxy peuvent obtenir le comportement par défaut en appelant les méthodes de l'objet Reflect, puis effectuer des opérations supplémentaires.

let exam = { name: "Tom", age: 24 } let handler = { get: function(target, key){ console.log("getting "+key); return Reflect.get(target,key); }, set: function(target, key, value){ console.log("setting "+key+" to "+value) Reflect.set(target, key, value); } } let proxy = new Proxy(exam, handler) proxy.name = "Jerry" proxy.name // affectation de name à Jerry // lecture de name // "Jerry"

Cas d'utilisation étendus

Implémentation du patron Observer.

//Définir une collection Set. const queuedObservers = new Set(); //Placer toutes les fonctions observatrices dans la collection Set. const observe = fn => queuedObservers.add(fn); //observable renvoie un proxy de l'objet d'origine et intercepte les opérations d'affectation. const observable = obj => new Proxy(obj, {set}); function set(target, key, value, receiver) { //Obtenir l'opération d'affectation de l'objet. const result = Reflect.set(target, key, value, receiver); //Exécuter tous les observateurs. queuedObservers.forEach(observer => observer()); //Exécuter l'opération d'affectation. return result; }