Easy Tutorial
❮ Android Tutorial Webview Error Code The Last Line Effect ❯

3.1.2 ES6 Reflect and Proxy

Category ES6 Tutorial

Overview

Proxy and Reflect are APIs introduced in ES6 for manipulating objects.

Proxy can intercept operations such as reading from and calling functions on a target object, and then handle these operations. It does not directly manipulate the object but acts like a proxy, operating through a proxy object of the target, allowing additional operations to be added during these actions.

Reflect can be used to obtain the behavior of a target object. It is similar to Object but is more readable and provides a more elegant way to manipulate objects. Its methods correspond to those of Proxy.


Basic Usage

Proxy

A Proxy object consists of two parts: target and handler. When generating an instance object through the Proxy constructor, these two parameters are required. target is the target object, and handler is an object that declares the specified behavior for the proxy 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)
// calling get method, the target object is empty and has no name property
proxyEpt.name // getting name
// calling set method, adding the name property to the target object
proxyEpt.name = 'Tom'
// setting name
// "Tom"
// calling get again, the name property now exists
proxyEpt.name
// getting name
// "Tom"

// When creating an instance through the constructor, it actually performs a shallow copy of the target object, so the target object and the proxy object will influence each other
targetEpt
// {name: "Tom"}

// The handler object can also be empty, which means no interception operations are set, 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 reading operations on the propKey of 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 private property reading protection
      if(propKey[0] === '_'){
          throw new Error(`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 assignment operations on the propKey of the target object. If a property of the target object is not writable and not configurable, the set method will not work.

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');
            }
        }
        // Save directly for properties that meet the conditions and other properties
        obj[prop] = value;
    }
};
let proxy = new Proxy({}, validator)
proxy.age = 100;
proxy.age           // 100
proxy.age = 'oppps' // throws an error
proxy.age = 300     // throws an error

The fourth parameter receiver represents the original object where the operation was performed, usually 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 that in strict mode, if the set proxy does not return true, it will throw an error.

apply(target, ctx, args)

Used to intercept function calls, call, and apply operations. target represents the target object, ctx represents the context of the target object, and args represents the array of arguments 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 HasProperty operations, i.e., when checking if the target object has the propKey property, this method will intercept it. This method does not distinguish between an object's own properties and inherited properties.

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 delete operations. 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.defineProperty operations. If the target object is not extensible, adding a property that does not exist on the target object will throw an error; if the property is not writable or not configurable, it 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"}

// If 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
// {}
getOwnPropertyDescriptor(target, propKey)

Used to intercept Object.getOwnPropertyDescriptor(). The return value is the property descriptor object or undefined.

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

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 to get the prototype of an object. This 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 that the return value must be an object or null, otherwise it will throw an error. Additionally, 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: The 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 operations to read the own properties of an object. This includes the following operations:

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

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

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

If the target object is non-extensible, all properties of the target object must be returned and only those properties can be returned, not including non-existent properties, otherwise an error will also be thrown.

let proxy = new Proxy({
  name: "Tom",
  age: 24
}, {
    ownKeys(target) {
        return ['name'];
    }
});
Object.keys(proxy)
// ['name']

The following types of properties are filtered out in the return result:

let target = {
  name: "Tom",
  [Symbol.for('age')]: 24,
};
// Adding 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)

Intercepts 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),
// proxy.preventExtensions can return true, otherwise an error will be thrown
var proxy = new Proxy({}, {
  preventExtensions: function(target) {
    return true;
  }
});
// Since proxy.preventExtensions returns true, this will also return true, thus throwing an error
Object.preventExtensions(proxy) // 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 are clearly part of the language's internal operations have been moved to the Reflect object (currently some methods exist both on the Object and Reflect objects). New methods will only be deployed on the Reflect object in the future.

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

The Reflect object implements Object's imperative operations in a functional way.

Static Methods

Reflect.get(target, name, receiver)

Looks up and returns 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 the receiver
let receiver = {
    name: "Jerry",
    age: 20
}
Reflect.get(exam, 'info', receiver); // Jerry20

// When the name is a property that does not exist in the target object, it returns undefined
Reflect.get(exam, 'birth'); // undefined

// When the target is not an object, an error is thrown
Reflect.get(1, 'name'); // TypeError
Reflect.set(target, name, value, receiver)

Sets the name property of the target to value. The return value is a boolean, true indicates successful modification, and false indicates failure. An error is thrown when the target is a non-existent object.

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 is cleared
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 the receiver, so the property being modified is actually the receiver's property
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)

A functional version of the name in obj directive, used to check if the name property exists in the obj object. The return value is a boolean. If obj is not an object, a TypeError is thrown.

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

is the functional equivalent of delete obj[property], used to delete the property attribute of the obj object, and returns a boolean value. If obj is not an object, it throws a TypeError.

let exam = {
    name: "Tom",
    age: 24
}
Reflect.deleteProperty(exam, 'name'); // true
exam // {age: 24}
// Returns true even if the property does not exist
Reflect.deleteProperty(exam, 'name'); // true
Reflect.construct(obj, args)

is equivalent to new target(...args).

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

is used to retrieve the __proto__ property of obj. It throws an error if obj is not an object, unlike Object which converts obj to an object.

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

is used to set the prototype of the target object.

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

is 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 list of arguments to be passed to the target function, which can be an array or an array-like object. If the target function cannot be called, it throws a TypeError.

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

is used to define a property on the target object. It throws an error if target is not an object.

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)

is used to get the descriptor object of the propertyKey property of the target object. It throws an error if target is not an object, and does not convert non-objects to objects.

var exam = {}
Reflect.defineProperty(exam, 'name', {
  value: true,
  enumerable: false,
})
Reflect.getOwnPropertyDescriptor(exam, 'name')
// { configurable: false, enumerable: false, value: true, writable: false}

// Returns undefined if the propertyKey does not exist in the target object
Reflect.getOwnPropertyDescriptor(exam, 'age') // undefined
Reflect.isExtensible(target)

is used to determine if the target object is extensible. It returns a boolean value. If target is not an object, it throws an error.

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

is used to make the target object non-extensible. It throws an error if target is not an object.

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

is used to return all properties of the target object, equivalent to the sum of Object.getOwnPropertyNames and Object.getOwnPropertySymbols.

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

Combining Usage

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 call the methods of the Reflect object to obtain the default behavior, 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"

Use Case Extension

Implementing Observer Pattern

// Define a Set collection
const queuedObservers = new Set();
// Add observer functions to the Set collection
const observe = fn => queuedObservers.add(fn);
// observable returns a proxy of the original object, intercepting 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;
}

#

-

** wusq

* wus**[email protected]

fn => queuedObservers.add(fn)

is equivalent to:

function(fn){ 
    return queuedObservers.add(fn) ;
};
obj => new Proxy(obj, {set})

is equivalent to:

function(obj){
    return new Proxy(obj, {set});
}
observer => observer()

is equivalent to:

function(observer ){
    return observer()
}

** wusq

* wus**[email protected]

** Click to Share Notes

Cancel

-

-

-

-1.1 ES6 Tutorial

-1.2 ES6 Environment Setup

-2.1 ES6 let and const

-2.2 ES6 Destructuring Assignment

-2.3 ES6 Symbol

-3.1.1 ES6 Map and Set

-3.2.1 ES6 Strings

-3.2.2 ES6 Numbers

-3.2.3 ES6 Objects

-3.2.4 ES6 Arrays

-4.1 ES6 Functions

-4.3 ES6 Class

-4.4 ES6 Modules

-5.1 ES6 Promise Object

-5.2 ES6 Generator Function

-5.3 ES6 async Function

Follow on WeChat

English:

❮ Android Tutorial Webview Error Code The Last Line Effect ❯