Overview

Proxy and Reflect are APIs introduced by ES6 for operating on objects.

Proxy can intercept operations on the target object, such as reading and function calls, and then perform operation processing. It does not directly operate on the object, but like the proxy pattern, it operates through a proxy object of the object, and during these operations, additional required operations can be added.

Reflect can be used to obtain the behavior of the target object. It is similar to Object, but more readable, providing a more elegant way to operate on objects. Its methods correspond to Proxy.


Basic Usage

Proxy

A Proxy object consists of two parts: target and handler. When creating an instance object through the Proxy constructor, these two parameters need to be provided. target is the target object, and handler is an object that declares the specified behavior for proxying target.

let target = { name: 'Tom', age: 24 } let handler = { get: function(target, key) { console.log('getting '+key); return target[key]; //Not target.key }, set: function(target, key, value) { console.log('setting '+key); target[key] = value; } } let proxy = new Proxy(target, handler) proxy.name //Actually executes handler.get proxy.age = 25 //Actually executes handler.set // getting name // setting age // 25 //target can be an empty object let targetEpt = {} let proxyEpt = new Proxy(targetEpt, handler) //Call the get method. At this time, the target object is empty and has no name property. proxyEpt.name // getting name //Call the set method to add the name property to the target object. proxyEpt.name = 'Tom' // setting name // "Tom" //Call get again. At this time, the name property already exists. proxyEpt.name // getting name // "Tom" //When creating a new instance through the constructor, it actually performs a shallow copy of the target object, so the target object and the proxy object will mutually //affect targetEpt // {name: "Tom"} //The handler object can also be empty, which is equivalent to not setting interception operations and directly accessing the target object. let targetEmpty = {} let proxyEmpty = new Proxy(targetEmpty,{}) proxyEmpty.name = "Tom" targetEmpty // {name: "Tom"}

Instance Methods

get(target, propKey, receiver)

Used for the read operation of propKey on the target object.

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

The get() method can be inherited.

let proxy = new Proxy({}, { get(target, propKey, receiver) { //Implement read protection for private properties if(propKey[0] === '_'){ throw new Erro(`Invalid attempt to get private "${propKey}"`); } console.log('Getting ' + propKey); return target[propKey]; } }); let obj = Object.create(proxy); obj.name // Getting name
set(target, propKey, value, receiver)

Used to intercept the assignment operation of propKey on the target object. If a property of the target object itself is non-writable and non-configurable, then the set method will have no effect.

let validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); } if (value > 200) { throw new RangeError('The age seems invalid'); } } //For the age property and other properties that meet the conditions, save them directly. obj[prop] = value; } }; let proxy= new Proxy({}, validator) proxy.age = 100; proxy.age // 100 proxy.age = 'oppps' //Error reported proxy.age = 300 //Error reported

The fourth parameter receiver represents the object where the original operation behavior is located, generally the Proxy instance itself.

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 // true

Note: in strict mode, if the set proxy does not return true, an error will be reported.

apply(target, ctx, args)

Used to intercept function calls, call, and reply operations. target represents the target object, ctx represents the target object context, and args represents the argument array of the target object.

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)

Used to intercept the HasProperty operation, that is, when determining whether the target object has the propKey property, it will be intercepted by this method. This method does not determine whether a property is an object's own property or an inherited property.

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

Note: This method does not intercept for...in loops.

construct(target, args)

Used to intercept the new command. The return value must be an object.

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)

Used to intercept the delete operation. If this method throws an error or returns false, the propKey property cannot be deleted by the delete command.

defineProperty(target, propKey, propDesc)

Used to intercept Object.definePro. If the target object is non-extensible, adding a property that does not exist on the target object will report an error; if a property is non-writable or non-configurable, these properties cannot be changed.

let handler = { defineProperty: function(target, propKey, propDesc){ console.log("handle defineProperty"); return true; } } let target = {} let proxy = new Proxy(target, handler) proxy.name = "Tom" // handle defineProperty target // {name: "Tom"} //When defineProperty returns false, the property addition operation is invalid. 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 operation

getOwnPropertyDescriptor(target, propKey)

Used to intercept Object.getOwnPropertyD(). The return value is a property descriptor object or 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 property

getPrototypeOf(target)

Mainly used to intercept operations that get the object prototype. Includes the following operations:

- 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) // {}

Note: the return value must be an object or null, otherwise an error will be reported. In addition, if the target object is non-extensible, the getPrototypeOf method must return the prototype object of the target object.

let proxy = new Proxy({},{ getPrototypeOf: function(target){ return true; } }) Object.getPrototypeOf(proxy) // TypeError: 'getPrototypeOf' on proxy: trap returned neither object // nor null
isExtensible(target)

Used to intercept the Object.isExtensible operation.

This method can only return a boolean value; otherwise, the return value will be automatically converted to a boolean.

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

Note: its return value must be consistent with the isExtensible property of the target object, otherwise an error will be thrown.

let proxy = new Proxy({},{ isExtensible:function(target){ return false; } }) Object.isExtensible(proxy) // TypeError: 'isExtensible' on proxy: trap result does not reflect // extensibility of proxy target (which is 'true')
ownKeys(target)

Used to intercept the read operation of an object's own properties. Mainly includes the following operations:

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

The array members returned by the method can only be strings or Symbol values, otherwise an error will be reported.

If the target object contains non-configurable properties, these properties must be returned in the result, otherwise an error will be reported.

If the target object is non-extensible, it must return all and only all properties contained in the target object; it cannot include non-existent properties, otherwise an error will also be reported.

let proxy = new Proxy( { name: "Tom", age: 24 }, { ownKeys(target) { return ['name']; } }); Object.keys(proxy) //[ 'name' ]f In the returned result, three types of properties will be filtered: //- Properties not present on the target object //- Properties whose names are Symbol values //- Non-enumerable properties let target = { name: "Tom", [Symbol.for('age')]: 24, }; //Add a non-enumerable property '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)

Intercept the Object.preventExtensions operation.

This method must return a boolean value, otherwise it will be automatically converted to a boolean.

//Only when the target object is non-extensible (i.e., Object.isExtensible(proxy) is false), //can proxy.preventExtensions return true; otherwise, an error will be reported var proxy = new Proxy({}, { preventExtensions: function(target) { return true; } }); //Because proxy.preventExtensions returns true, it also returns true here, so an error will be reported Object.preventExtensions(proxy)By// TypeError: 'preventExtensions' on proxy: trap returned truish but // the proxy target is extensible //Solution var proxy = new Proxy({}, { preventExtensions: function(target) { //Call Object.preventExtensions before returning Object.preventExtensions(target); return true; } }); Object.preventExtensions(proxy) // Proxy {}
setPrototypeOf

Mainly used to intercept the Object.setPrototypeOf method.

The return value must be a boolean, otherwise it will be automatically converted to a boolean.

If the target object is non-extensible, the setPrototypeOf method must not change the prototype of the target object.

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

Used to return a revocable Proxy instance.

let {proxy, revoke} = Proxy.revocable({}, {}); proxy.name = "Tom"; revoke(); proxy.name // TypeError: Cannot perform 'get' on a proxy that has been revoked

Reflect

In ES6, some methods of Object that clearly belong to the language internals were moved to the Reflect object (currently some methods exist on both the Object and Reflect objects), and future new methods will be deployed only on the Reflect object.

The Reflect object has modified the return results of some methods to make them more reasonable.

The Reflect object uses functions to implement Object's imperative operations.

Static Methods

Reflect.get(target, name, receiver)

Find and return the name property of the target object.

let exam = { name: "Tom", age: 24, get info(){ return this.name + this.age; } } Reflect.get(exam, 'name'); // "Tom" //When the target object has a getter method for the name property, the this of the getter method will be bound to // receiver let receiver = { name: "Jerry", age: 20 } Reflect.get(exam, 'info', receiver); // Jerry20 //When name is a property that does not exist on the target object, returns undefined Reflect.get(exam, 'birth'); // undefined //When target is not an object, an error will be reported Reflect.get(1, 'name'); // TypeError
Reflect.set(target, name, value, receiver)

Set the name property of target to value. The return value is boolean; true indicates success, false indicates failure. When target is a non-existent object, an error will be reported.

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 //When value is empty, the name property will be removed. Reflect.set(exam, 'age', ); // true exam.age; // undefined //When the target object has a setter method for the name property, the this in the setter method will be bound to // receiver, so what is actually modified is the property of 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)

It is the functional form of the name in obj operator, used to check whether the name property exists in the obj object. The return value is boolean. If obj is not an object, a TypeError will be reported.

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

It is the functional form of delete obj[property], used to delete the property property of the obj object. The return value is boolean. If obj is not an object, a TypeError will be reported.

let exam = { name: "Tom", age: 24 } Reflect.deleteProperty(exam , 'name'); // true exam // {age: 24} //When property does not exist, it also returns true Reflect.deleteProperty(exam , 'name'); // true
Reflect.construct(obj, args)

Equivalent to new target(...args).

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

Used to read the _proto_ property of obj. When obj is not an object, it does not convert obj to an object like Object does, but instead reports an error.

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

Used to set the prototype of the target object.

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

Equivalent to Function.prototype.apply.call(func, thisArg, args). func represents the target function; thisArg represents the this object bound to the target function; args represents the argument list passed when the target function is called, which can be an array or an array-like object. If the target function cannot be called, a TypeError is thrown.

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

Used to define properties for the target object. If target is not an object, an error is thrown.

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)

Used to obtain the descriptor object of the propertyKey property of the target object. When target is not an object, an error is thrown to indicate an invalid parameter; the non-object is not converted to an object.

var exam = {} Reflect.defineProperty(exam, 'name', { value: true, enumerable: false, }) Reflect.getOwnPropertyDescriptor(exam, 'name') // { configurable: false, enumerable: false, value: true, writable: // false} //If the propertyKey property does not exist on the target object, returns undefined. Reflect.getOwnPropertyDescriptor(exam, 'age') // undefined
Reflect.isExtensible(target)

Used to determine whether the target object is extensible. The return value is a boolean. If the target parameter is not an object, an error is thrown.

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

Used to make the target object non-extensible. If the target parameter is not an object, an error is thrown.

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

Used to return all properties of the target object, equivalent to the combination of Object.getOwnPropertyNames and Object.getOwnPropertySymbols.

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

Combined Use

The methods of the Reflect object correspond one-to-one with the methods of the Proxy object. Therefore, the methods of the Proxy object can obtain default behavior by calling the methods of the Reflect object, and then perform additional operations.

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 // setting name to Jerry // getting name // "Jerry"

Expanded Use Cases

Implementing the Observer pattern.

//Define a Set collection. const queuedObservers = new Set(); //Put all observer functions into the Set collection. const observe = fn => queuedObservers.add(fn); //observable returns a proxy of the original object and intercepts assignment operations. const observable = obj => new Proxy(obj, {set}); function set(target, key, value, receiver) { //Get the assignment operation of the object. const result = Reflect.set(target, key, value, receiver); //Execute all observers. queuedObservers.forEach(observer => observer()); //Execute the assignment operation. return result; }