The basic concept

Intercepts operations on specified objects and overrides default behaviors with preset behaviors to filter and rewrite external access (such as attribute lookup, assignment, enumeration, function call, etc.).

new Proxy(target, handler);

  • A target is a target object wrapped in a Proxy (this can be any type of object, including a native array, a function, or even another Proxy)
  • Handler holds a number of placeholder objects for specific properties that are functions that define the behavior of the agent when an operation is performed
let targetObj = {time:30}; Let proxy = new proxy (targetObj, {get: (target, property) => 35}); let proxy1 = new Proxy(targetObj, {}); proxy.time; Proxy1.time; proxy1.time; // 30 /// If handler is not set, the behavior of proxy object proxy1 is the same as that of target object proxy1.foo = 'foo'; targetObj.foo; // Foo's changes to the proxy object affect the target proxy; // Proxy {time: 30, foo: "foo"} // Proxy {time: 30, foo: "foo"} // Proxy {time: 30, foo: "foo"} // 35 proxy.foo = 'bar'; // Directly modify the target object proxy1.foo; // Bar proxy1 does not intercept, so its behavior is consistent with the target objectCopy the code

It can be seen from the above that the Proxy object is very similar to the operation object that sets an alias for the target object. The external access to the object must first pass this layer of interception. This layer of interception can filter and rewrite the external access

let targetObj = {time:30}; let proxy2 = new Proxy(targetObj, { get: (target, property) => 35, set: (target, key, value, receiver) => { console.log(target); // Target object console.log(key); // The property name to be set console.log(value); // Set the new value console.log(receiver); Proxy2 target[key] = '${value}'; }}); proxy2.bar = '22'; targetObj.bar; // Value set by proxy2:22Copy the code

Interception supported by Proxy

  • getPrototypeOf(target)This method is called when the prototype of the proxy object is read
    • Object.prototype.__proto__.Object.prototype.isPrototypeOf.Object.getPrototypeOf.Reflect.getPrototypeOf().instanceofTriggers the agent method to run
    • Parameters:
      • targetThe target object to be proxied
    • Return value: Must return an object value or NULL
let info = {name: 'rede'} let foo = Object.create(info); Let handler = {getPrototypeOf(target) {console.log(' trigger interceptor message '); return Reflect.getPrototypeOf(target) } } let proxy = new Proxy(foo, handler); proxy.__proto__; // Trigger interceptor info {name: "rede"} info.isPrototypeof (proxy); // Trigger interceptor message true object.getProtoTypeof (proxy); // Trigger interceptor information {name: "rede"} reflect.getProtoTypeof (proxy); // proxy instanceof Object; // Trigger interceptor message trueCopy the code

The return value of the interceptor must be an object or NULL, otherwise an error will be reported

let handler = { getPrototypeOf(target) { return false; }}; let proxy = new Proxy({}, handler); proxy.__proto__; // Uncaught TypeError ... let handler = { getPrototypeOf(target) { return null; }}; let proxy = new Proxy({}, handler); proxy.__proto__; // null returns nullCopy the code
  • setPrototypeOf(target, proto)This method is called when a stereotype is set for an existing object
    • Object. SetPrototypeOf, Reflect. SetPrototypeOfTriggers the agent method to run
    • Parameters:
      • targetIntercepted object
      • protoObject new prototype or NULL
    • Return value: Boolean. If the target object is a function, there are two additional operations that can be intercepted
let info = {name:'info', _name:'name', info:'info'}; let obj = {name:'obj'}; Let proxy = new proxy (obj, {setPrototypeOf (target, proto) {console.log(' trigger interceptor method: setPrototypeOf'); if (Object.isExtensible(target)) { return Reflect.setPrototypeOf(target, proto); }}}); . Object.setPrototypeOf(proxy, info); // Proxy {name: "obj"} proxy._name; // Proxy {name: "obj"} Proxy. // nameCopy the code

Here we are intercepting the setup prototype, and the intercepting behavior is to specify a new prototype object for the intercepting object if the target object is extensible.

The reason for this is that the interceptor has a special rule that if the object is not extensible, you cannot set the prototype to change the target

Object.preventExtensions(obj); SetPrototypeOf (proxy, info); // Set the target Object to be unextensible. // Uncaught TypeError: 'setPrototypeOf' on proxy: trap returned falsishCopy the code

At this point, if we try to modify the obj prototype again, an error will be reported. A simple way to do this is to throw an error like this

proxy = new Proxy(obj, { setPrototypeOf (target, proto) { if (! Object.isextensible (target)) {throw new Error(' This Object cannot be extensible, new stereotypes cannot be specified '); } return Reflect.setPrototypeOf(target, proto); }})Copy the code
  • isExtensible(target)
    • Object. IsExtensible, Reflect. IsExtensibleTriggers the run of the proxy method, which determines whether the target object is extensible
    • Parameters:
      • targetThe target object
    • Return value: Boolean (This method can only return a Boolean, otherwise the return value is automatically converted to a Boolean)
let info = {name:'rede'}; Let Handler = {isExtensible(target) {console.log(' Trigger interception method: isExtensible'); return true; }}; let proxy = new Proxy(info, handler); Object.isExtensible(proxy); // Trigger intercessor methods: isExtensible True... let info = {name:'rede'}; Let Handler = {isExtensible(target) {console.log(' Trigger interception method: isExtensible'); return 1; }}; let proxy = new Proxy(info, handler); Object.isExtensible(proxy); IsExtensible True (1 to true)... /// The return value of this method must be consistent with the isExtensible property of the target Object, otherwise the object.freeze (info) error will be thrown; // Set the target Object to object.isextensible (info); // false Object.isExtensible(proxy); // Uncaught TypeError: 'isExtensible' on proxy proxy = new proxy (info, {isExtensible(target) {console.log(' isExtensible'); return false; }}); Object.isExtensible(proxy); // Trigger interception methods: isExtensible TrueCopy the code
  • preventExtensions(target)
    • Object. PreventExtensions, Reflect. PreventExtensionsTriggers the running of the proxy method by making the target object unextensible
    • Parameters:
      • targetThe target object
    • Return value: Boolean true for set success, false for set failure, but cannot be true/false directly
let obj = {name:'name'} let proxy = new Proxy(obj, {preventExtensions(target) {if (reflect.isextensible (target)) {throw new Error(' This object cannot do this ')} return true; }}); Object.preventExtensions(proxy); // Uncaught Error: This object cannot perform this operation /// This behavior can be interpreted as the proxy object retains the same characteristics as the target object. If the proxy object is set to true or false without verification, there will be a difference between the proxy object and the target objectCopy the code

If you don’t want an object to be made unextensible, you can use this interceptor to intercept it.

The interceptor has a special requirement that it cannot be extended if the target object is. The return must be true or TypeError is reported

let obj = {name:'name'} Object.preventExtensions(obj); Let proxy = new proxy (obj, {preventExtensions(target) {if (reflect.isextensible (target)) {throw new Error(' This object cannot do this ')} return false; // Deliberately set to false, the target object should be unextensible, in which case it must return true}}); Object.preventExtensions(proxy); // Uncaught TypeError: 'preventExtensions' on proxy: trap returned falsishCopy the code
  • getOwnPropertyDescriptor(target, propKey)
    • Object. GetOwnPropertyDescriptor, Reflect. GetOwnPropertyDescriptorTriggers the run of the proxy method that returns a description object for the property
    • Parameters:
      • targetThe target object
      • propKeyThe name of the property describing the object to be returned
    • Return value: Object or undefined
let handler = { getOwnPropertyDescriptor (target, propKey) { if (propKey[0] === '_') { return; } return Reflect.getOwnPropertyDescriptor(target, propKey); } } let obj = {info: 'info', _info: 'info'}; let proxy = new Proxy(obj, handler); Object.getOwnPropertyDescriptor(proxy, '_info'); // undefined // the above behavior is equivalent to the protection of private attributes, attributes that start with '_' cannot be seen directlyCopy the code
  • defineProperty(target, property, descriptor)
    • Object.defineproperty, Object.defineProperties, Reflect.defineProperty, proxy.property='value'Triggers the running of the proxy method by setting properties directly on the target object
    • Parameters:
      • targetThe target object
      • propertyThe name of the property to retrieve
      • descriptorDescription of the property to be defined or modified
        • descriptorWe only acceptEnumerable, different, writable, value, get, setThese values are set and the other values are ignored
    • Return value: Boolean indicates whether the property operation succeeded
let obj = new Proxy({}, { defineProperty (target, property, Descriptor) {if (property.slice(0,1) === '_') {throw new Error(' disallow new private attributes '); } return Reflect.defineProperty(target, property, descriptor); }}); obj.foo = 'foo'; // obj._foo = 'foo'; // Uncaught Error: Disables the newly added private attributeCopy the code

Using this approach to better standardize code writing for attribute definitions, the interceptor can also intercept Settings for Object.defineProperties

Object.defineProperties(obj, { bar: {value:'bar'}, _info: {value:'info'} }); // Uncaught Error: Disallow the newly added private property obj; // Proxy {bar: "bar"} the bar attribute will normally add... Object.defineproperties (obj, {_bar: {value:'bar'}, info: {value:'info'}}); // Uncaught Error: Disallow the newly added private property obj; // Proxy {bar: "bar"} info Failed to be setCopy the code

If false is returned, the attribute will also fail to be added

let handler = { defineProperty (target, key, descriptor) { return false; }}; let target = {}; let proxy = new Proxy(target, handler); proxy.foo = 'bar'; proxy.foo; // undefinedCopy the code
  • has(target, propKey)
    • Attribute query: foo in proxy, inheritance attribute query: foo in object.create (proxy), reflect.hasTriggers the proxy method to run, depending on whether there are related properties directly on the target object
    • Parameters:
      • targetThe target object
      • propKeyThe name of the property to be detected
    • Return value: Boolean
Let proxy = new proxy ({time: 30, foo: "bar"}, {has: (target, propKey) => {if (propKey[0] === '_') {return false; } return propKey in target; } }) proxy; // Proxy {time: 30, foo: "bar"} proxy.name = 'name'; 'name' in proxy; // true proxy._name = 'name'; '_name' in proxy; // false In this case, the _name attribute is an existing proxy; // Proxy {time: 30, foo: "bar", name: "name", _name: "name"}Copy the code

Constraint: If a property of the target object itself is not configurable (or if the target object is not extensible), the property cannot be hidden by the proxy

let obj = {name:'obj', info: 'info'}; Object.preventExtensions(obj); let proxy = new Proxy(obj, { has: (target, propKey) => { return false }, }); 'name' in obj; // true 'name' in proxy; // Uncaught TypeError // obj is not configurable, so the proxy method cannot return falseCopy the code
  • get(target, propKey, receiver)
    • Access properties: proxy[foo] and proxy.bar, access properties on the prototype chain: object.create (proxy)[foo], reflect.getTriggers the running of the proxy method, which reads properties on the target object
    • Parameters:
      • targetThe target object
      • propKeyThe property name
      • receiverproxyInstantiate itself, this parameter is optional
    • Return value: any value
Let person = {name: "name"}; let proxy = new Proxy(person, { get: function(target, property) { if (property in target) { return target[property]; } else { throw new ReferenceError(`Property ${property} does not exist.`); }}}); proxy.name; / / zhang SAN proxy. The age; // Uncaught ReferenceError: Property "age" does not exist. person.age; // undefined // proxy Because of the existence of the interceptor, so access to non-existent attributes of the error, otherwise only return undefinedCopy the code

The set GET property can be inherited

let obj = Object.create(proxy);
obj.age; // Uncaught ReferenceError: Property "age" does not exist.
Copy the code

The interception of the GET feature allows us to write something more imaginative. For example, when a project is running, we usually differentiate between different environments and can return different information according to the environment

let env = 'stg'; let proxy = new Proxy({}, { get (targetObj, propKey) { if (propKey === 'info' && env) { return env === 'stg' ? 'Project running in STG environment' : 'Project running in non-STG environment '; } return targetObj[propKey] } }); proxy.name; // undefined proxy.info; // Project running in STG env = 'PRD '; // Suppose the environment is PRD proxy.info; // The project runs on a non-STG environment /// you can use something like this to achieve the logic of running different code in different environmentsCopy the code
  • set(target, propKey, value, receiver)
    • Create (proxy)[foo] = bar, reflect.setTriggers the running of the proxy method by setting properties on the target object
    • Parameters:
      • targetThe target object
      • propKeyThe name of the property being set
      • valueThe new value to be set
      • receiverThe object that was originally called
    • Return value: Boolean
Let obj = {name:'obj'}; let obj = {name:'obj'}; Let proxy = new proxy (obj, {get (target, propKey) {if (propKey[0] === '_') {throw new ReferenceError(" private attribute is not allowed to access "); } return target[propKey]; }, set (target, propKey, value) {if (propKey[0] === '_') {throw new ReferenceError(" private attribute is not allowed to access "); } target[propKey] = value; }}); proxy.name; // obj proxy._name; // Uncaught ReferenceError: private attribute prevents access to proxy._name = 'info'; // Uncaught ReferenceError: Private property prevents access /// so that some access/setting restrictions can be added through the proxyCopy the code
  • deleteProperty(target, propKey)
    • Delete properties: delete proxy[foo] and delete proxy.foo and reflect. deletePropertyTriggers the running of the proxy method by deleting properties on the target object
    • Parameters:
      • targetThe target object
      • propKeyAttributes to be deleted
    • Return value: Boolean
// let obj = {name:'obj', _name: 'info'}; let proxy = new Proxy(obj, { has: (target, propKey) => { return Reflect.has(target, propKey); }, deleteProperty: If (propKey[0] === '_') {throw new Error(' this property is private and cannot be removed '); } delete target[propKey]; // Delete the relevant key from the target object console.log(' delete succeeded '); return true; }}); delete proxy.name; // delete proxy successfully // true delete proxy._name; // Uncaught Error: This attribute is private and cannot be deletedCopy the code
  • ownKeys(target)
    • Object. GetOwnPropertyNames, Object) getOwnPropertySymbols, Object) keys, Reflect. OwnKeysTriggers the proxy method to run by getting an array of the target object’s own property keys
    • Parameters:
      • targetThe target object
    • Return value: enumerable object
let obj = {info:'info', _name:'name', bar:'bar'} let handler = { ownKeys (target) { return Reflect.ownKeys(target).filter(key => key[0] ! = = '_'); }}; let proxy = new Proxy(obj, handler); Object.getOwnPropertyNames(proxy); // ["info", "bar"] // The interceptor's behavior at this point is to return attributes that do not begin with '_'Copy the code

OwnKeys If the result is returned explicitly, the result must be an array, and the elements in the array must be of type String or Symbol

let obj = {info:'info', _name:'name', bar:'bar'}; Object.defineProperty(obj, 'key', { enumerable: false, value: 'static' }); let handler = { ownKeys (target) { return false; } } let proxy = new Proxy(obj, handler); Object.getOwnPropertyNames(proxy); // Uncaught TypeError... The Boolean value returned by ownKeys at this pointCopy the code

The ownKeys method does not return nonexistent attributes, Symbol values, and non-traversable attributes

let obj = {info:'info', _name:'name', bar:'bar'}; DefineProperty (obj, 'key', {enumerable: false, writable: true, value: 'static'}); let handler = { ownKeys (target) { return ['info', '_info', '_name', 'bar', 'key', Symbol.for('secret')]; }} let proxy = new proxy (obj, handler); Object.keys(proxy); // ["info", "_name", "bar"] no attribute, Symbol, no traversal attribute is returnedCopy the code

Use Object. GetOwnPropertyNames without filter does not exist and do not traverse the attribute, but will filter the Symbol value

Object.getOwnPropertyNames(proxy); // ["info", "_info", "_name", "bar", "key"] ... / / Object. GetOwnPropertySymbols will return only Symbol value Object. GetOwnPropertySymbols (proxy); // [Symbol(secret)] ... // for... For (let key in proxy) {console.log(key); } // info // _name // barCopy the code

If the target object is not extensible, ownKeys sets the return array to be only the properties of the target object, and cannot have redundant properties or an error will be reported

let obj = {name:'name', _name:'name'} Object.freeze(obj); let proxy = new Proxy(obj, { ownKeys (target) { return ['name', '_name', 'key'] } }); Object.keys(proxy); // Uncaught TypeError // If the target object is not frozen, the returned array should be a filtered arrayCopy the code
  • apply(target, object, args)
    • proxy(... Function. Prototype. Apply, Function. Prototype. Call, ReflectTriggers the run of the proxy method by invoking the target function with the specified argument list
    • Parameters:
      • Target: indicates the target object
      • Object: Context object when called (this)
      • Args: Array of arguments when called
    • Return value: any value
Let handler = {apply (target, CTX, args) {if (args. Length > 2) {throw new Error(' this method only accepts two parameters, please avoid input too many parameters '); } return Reflect.apply(target, ctx, args); } } let sum = function (num1, num2) { return num1 + num2; } let proxy = new Proxy(sum, handler); proxy(1, 2); // 3 proxy(1, 2, 3); // Uncaught Error: This method accepts only two parameters. Please avoid entering too many parameters... Let handler = {apply (target, CTX, args) {if (ctx.version < 10) {throw new Error(' current version does not support this method '); } return Reflect.apply(target, ctx, args); } } let sum = function (num1, num2) { return num1 + num2; } let proxy = new Proxy(sum, handler); let obj = {version: 9, proxy}; Obj. Proxy (1, 2); // Uncaught Error: the current version does not support this method obj.version = 10; // update the virtual version number obj.proxy(1,2); As you can see from the above example, the second parameter to apply is the context of the Proxy instantiation object, in this case the obj objectCopy the code
  • construct(target, args, newTarget)
    • new proxy(... The args), Reflect the constructTriggers the run of the proxy method, which is equivalent tonewoperation
    • Parameters:
      • targetThe target object
      • argsThe list of parameters
      • newTargetThe constructor originally called
    • Return value: must be an object
let handler = { construct (target, args, NewTarget) {if (args [0] && Object. The prototype. The toString. Call (args [0]) = = = '[Object String]' && args [0]. Slice (0, 4)! == 'mgs_') {throw new Error(' first parameter name is invalid, please start with mgs_'); } return new target(... args); } } let obj = new Proxy(function(){}, handler); // If the first argument is a character, the character must start with 'msg_' new obj('info'); // Uncaught Error: The first parameter name is invalid, please start with mgs_ // We can write to this interception method if we want to do some verification on the object initializationCopy the code

methods

  • Proxy.revocable(target, handler)Create a destructible proxy object
    • Parameters:
      • targetThe target object that will be encapsulated by Proxy. It can be any type of object, including a native array, a function, or even another proxy object
      • handlerAn object whose properties are a set of optional functions that define the behavior of the agent when the corresponding operation is performed
    • Return value: Returns a revocable Proxy object that contains the Proxy object itself and its undo method
      • Its structure is:{"proxy": proxy, "revoke": revoke}
        • proxyRepresents the newly generated proxy object itself, and in the general waynew Proxy(target, handler)The proxy object created is no different, except that it can be destroyed. Right
        • revokeThe undo method is called to undo the proxy object it was created with without any arguments
let obj = {name:'name'}; let {proxy:info, revoke} = Proxy.revocable(obj, {}); info.name; // name revoke(); // Destroy the proxy object info.name; // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revokedCopy the code