Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

ES6 Proxy


May 08, 2021 ES6


Table of contents


1. Overview

Proxy is used to modify the default behavior of certain operations, equivalent to making changes at the language “元编程”( that is, programming the programming language.

Proxy can be understood to set up a “拦截” outside access to the object must first pass through this layer of interception, thus providing a mechanism to filter and rewrite outside access. The word Proxy is meant to be a proxy, and here it is used to "proxy" certain operations, which can be translated as "agents".

  1. var obj = new Proxy({}, {
  2. get: function (target, propKey, receiver) {
  3. console.log(`getting ${propKey}!`);
  4. return Reflect.get(target, propKey, receiver);
  5. },
  6. set: function (target, propKey, value, receiver) {
  7. console.log(`setting ${propKey}!`);
  8. return Reflect.set(target, propKey, value, receiver);
  9. }
  10. });

The code above sets up a layer of interception on an empty object, redefining the 读取 (get) and set 设置 behavior of the property. H ere for the time being, we will not explain the specific syntax, but only the results of the operation. For the object obj, which has an intercept behavior set, read and write its properties, and you get the following results.

  1. obj.count = 1
  2. // setting count!
  3. ++obj.count
  4. // getting count!
  5. // setting count!
  6. // 2

As the code above explains, Proxy 重载 overloads the point operator, which overrides the original definition of the language with its own definition.

ES6 natively provides a Proxy constructor to generate a Proxy instance.

  1. var proxy = new Proxy(target, handler);

All usages of the Proxy object are in this form above, but the difference is handler parameter is written. Where new Proxy() means to generate a Proxy instance, the target parameter represents the target object to be intercepted, and the handler parameter is also an object to customize the blocking behavior.

Here is another example of blocking read property behavior.

  1. var proxy = new Proxy({}, {
  2. get: function(target, propKey) {
  3. return 35;
  4. }
  5. });
  6. proxy.time // 35
  7. proxy.name // 35
  8. proxy.title // 35

In the code above, as a constructor, Proxy accepts two parameters. T he first argument is the target object to be proxyd (the example above is an empty object), i.e. if there is no involvement of Proxy, the operation was originally to access this object, and the second argument is a configuration object, for each agent operation, need to provide a corresponding handler, the function will intercept the corresponding operation. F or example, in the code above, the configuration object has a get method that intercepts access requests to the target object properties. T he two parameters of the get method are the target object and the property to be accessed. As you can see, because the intercept function always returns 35, you get 35 to access any property.

Note that for Proxy to work, you must work on the Proxy instance (in the case of the proxy object above) rather than on the target object (the above example is an empty object).

If handler does not set any intercepts, it is equivalent to going directly to the original object.

  1. var target = {};
  2. var handler = {};
  3. var proxy = new Proxy(target, handler);
  4. proxy.a = 'b';
  5. target.a // "b"

In the code above, handler is an empty object with no blocking effect, and accessing proxy is equivalent to accessing target.

One trick is to set the Proxy object to the object.proxy property so that it can be called on the object object.

  1. var object = { proxy: new Proxy(target, handler) };

The Proxy instance can also be used as a prototype object for other objects.

  1. var proxy = new Proxy({}, {
  2. get: function(target, propKey) {
  3. return 35;
  4. }
  5. });
  6. let obj = Object.create(proxy);
  7. obj.time // 35

In the code above, the proxy object is a prototype of the obj object, and the obj object itself does not have a time property, so according to the prototype chain, the property is read on the proxy object, resulting in interception.

The same interceptor function can be set to intercept multiple operations.

  1. var handler = {
  2. get: function(target, name) {
  3. if (name === 'prototype') {
  4. return Object.prototype;
  5. }
  6. return 'Hello, ' + name;
  7. },
  8. apply: function(target, thisBinding, args) {
  9. return args[0];
  10. },
  11. construct: function(target, args) {
  12. return {value: args[1]};
  13. }
  14. };
  15. var fproxy = new Proxy(function(x, y) {
  16. return x + y;
  17. }, handler);
  18. fproxy(1, 2) // 1
  19. new fproxy(1, 2) // {value: 2}
  20. fproxy.prototype === Object.prototype // true
  21. fproxy.foo === "Hello, foo" // true

For operations that can be set, but not blocked, they fall directly on the target object, producing results the way they were.

Below is a list of 13 interceptions supported by Proxy.

  • get (target, propKey, receiver): Intercepts reads of object properties such as proxy.foo and proxy .'foo'.
  • set (target, propKey, value, receiver): Intercepts the settings of object properties, such as proxy.foo s v or proxy s'foo' s v, returning a Boolean value.
  • has (target, propKey): Intercepts the operation of propKey in proxy and returns a Boolean value.
  • deleteProperty (target, propKey): Intercepts the operation of delete (proxy propKey) and returns a Boolean value.
  • ownKeys (target): Intercept Object.getOwnPropertyNames (proxy), Object.getOwnPropertySymbols (proxy), Object.keys (proxy), for... i n loop, returning an array. The method returns the property name of all its own properties of the target object, while object.keys() return results that include only the traversable properties of the target object itself.
  • getOwnPropertyDescriptor (target, propKey): Intercept Object.getOwnPropertyDescriptor (proxy, propKey) and return the description object of the property.
  • defineProperty (target, propKey, propDesc): Intercept Object.defineProperty (proxy, propKey, propDesc), Object.defineProperties (proxy, propDescs), return a Boolean value.
  • PreventExtensions (target): Intercept Object.preventExtensions (proxy) and return a Boolean value.
  • getPrototypeOf (target): Intercept Object.getPrototypeOf (proxy) and return an object.
  • IsExtensible (target): Intercept Object.isExtensible (proxy) and return a Boolean value.
  • setPrototypeOf (target, proto): Intercept Object.setPrototypeOf (proxy, proto) and return a Boolean value. If the target object is a function, there are two additional operations that can be blocked.
  • apply (target, object, args): Intercepts actions called by a Proxy instance as a function, such as proxy (... a rgs) 、 proxy.call(object, ... args) 、 proxy.apply(...) 。
  • construct (target, args): Intercepts operations called by a Proxy instance as a constructor, such as new proxy (... args) 。

2. The method of the Proxy instance

Below is a detailed description of these interception methods above.

get()

get method is used to intercept the read 读取 and can accept three parameters, followed by the target object, the property name, and the proxy instance itself (strictly speaking, the object against which the operation behavior is directed), the last of which is optional.

The use of the get method, which already has an example above, is another example of intercepting read operations.

  1. var person = {
  2. name: "张三"
  3. };
  4. var proxy = new Proxy(person, {
  5. get: function(target, propKey) {
  6. if (propKey in target) {
  7. return target[propKey];
  8. } else {
  9. throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
  10. }
  11. }
  12. });
  13. proxy.name // "张三"
  14. proxy.age // 抛出一个错误

The code above indicates that an error is thrown if the target object is accessed for a property that does not exist. Without this intercept function, access to properties that do not exist will only return undefined.

The get method can be inherited.

  1. let proto = new Proxy({}, {
  2. get(target, propertyKey, receiver) {
  3. console.log('GET ' + propertyKey);
  4. return target[propertyKey];
  5. }
  6. });
  7. let obj = Object.create(proto);
  8. obj.foo // "GET foo"

In the code above, the intercept operation is defined above the Prototype object, so if you read the properties inherited by the obj object, the intercept takes effect.

The following example uses get blocking to enable an array to read a negative index.

  1. function createArray(...elements) {
  2. let handler = {
  3. get(target, propKey, receiver) {
  4. let index = Number(propKey);
  5. if (index < 0) {
  6. propKey = String(target.length + index);
  7. }
  8. return Reflect.get(target, propKey, receiver);
  9. }
  10. };
  11. let target = [];
  12. target.push(...elements);
  13. return new Proxy(target, handler);
  14. }
  15. let arr = createArray('a', 'b', 'c');
  16. arr[-1] // c

In the code above, the position parameter of the array is -1, which outputs the penultimate member of the array.

Proxy you to convert an operation (get) that reads a property into a function that enables a chained operation of the property.

  1. var pipe = function (value) {
  2. var funcStack = [];
  3. var oproxy = new Proxy({} , {
  4. get : function (pipeObject, fnName) {
  5. if (fnName === 'get') {
  6. return funcStack.reduce(function (val, fn) {
  7. return fn(val);
  8. },value);
  9. }
  10. funcStack.push(window[fnName]);
  11. return oproxy;
  12. }
  13. });
  14. return oproxy;
  15. }
  16. var double = n => n * 2;
  17. var pow = n => n * n;
  18. var reverseInt = n => n.toString().split("").reverse().join("") | 0;
  19. pipe(3).double.pow.reverseInt.get; // 63

After setting up Proxy in the code above, the effect of chaining function names is achieved.

The following example is using get blocking to implement a common function dom that generates various DOM nodes.

  1. const dom = new Proxy({}, {
  2. get(target, property) {
  3. return function(attrs = {}, ...children) {
  4. const el = document.createElement(property);
  5. for (let prop of Object.keys(attrs)) {
  6. el.setAttribute(prop, attrs[prop]);
  7. }
  8. for (let child of children) {
  9. if (typeof child === 'string') {
  10. child = document.createTextNode(child);
  11. }
  12. el.appendChild(child);
  13. }
  14. return el;
  15. }
  16. }
  17. });
  18. const el = dom.div({},
  19. 'Hello, my name is ',
  20. dom.a({href: '//example.com'}, 'Mark'),
  21. '. I like:',
  22. dom.ul({},
  23. dom.li({}, 'The web'),
  24. dom.li({}, 'Food'),
  25. dom.li({}, '…actually that\'s it')
  26. )
  27. );
  28. document.body.appendChild(el);

The following is an example of a third parameter of the get method, which always points to the object where the original read operation is located, typically the Proxy instance.

  1. const proxy = new Proxy({}, {
  2. get: function(target, key, receiver) {
  3. return receiver;
  4. }
  5. });
  6. proxy.getReceiver === proxy // true

In the code above, the getReceiver property of the proxy object is provided by the proxy object, so receiver points to the proxy object.

  1. const proxy = new Proxy({}, {
  2. get: function(target, key, receiver) {
  3. return receiver;
  4. }
  5. });
  6. const d = Object.create(proxy);
  7. d.a === d // true

In the code above, the d object itself does not have an a property, so when you read d.a, you look for the prototype proxy object of d. At this point, receiver points to d, which represents the object where the original read operation is located.

If a property is not configurable and cannot be written, Proxy cannot modify the property, otherwise accessing the property through the Proxy object will report an error.

  1. const target = Object.defineProperties({}, {
  2. foo: {
  3. value: 123,
  4. writable: false,
  5. configurable: false
  6. },
  7. });
  8. const handler = {
  9. get(target, propKey) {
  10. return 'abc';
  11. }
  12. };
  13. const proxy = new Proxy(target, handler);
  14. proxy.foo
  15. // TypeError: Invariant check failed

set()

set method is used to intercept the assignment of a property and can accept four parameters, followed by the target object, property name, property value, and the Proxy instance itself, the last of which is optional.

Assuming that the Person object has an age property that should be an integer not greater than 200, you can use Proxy to ensure that the property value of age meets the requirements.

  1. let validator = {
  2. set: function(obj, prop, value) {
  3. if (prop === 'age') {
  4. if (!Number.isInteger(value)) {
  5. throw new TypeError('The age is not an integer');
  6. }
  7. if (value > 200) {
  8. throw new RangeError('The age seems invalid');
  9. }
  10. }
  11. // 对于满足条件的 age 属性以及其他属性,直接保存
  12. obj[prop] = value;
  13. }
  14. };
  15. let person = new Proxy({}, validator);
  16. person.age = 100;
  17. person.age // 100
  18. person.age = 'young' // 报错
  19. person.age = 300 // 报错

In the above code, because the memory function set is set, any non-compliant age property assignment throws an error, which is an implementation of data validation. The set method also allows you to bind data, i.e. update the DOM automatically whenever an object changes.

Sometimes, we set internal properties on top of an object, and the first character of the property name begins with an underscore, indicating that these properties should not be used externally. By combining the get and set methods, you can prevent these internal properties from being read and written externally.

  1. const handler = {
  2. get (target, key) {
  3. invariant(key, 'get');
  4. return target[key];
  5. },
  6. set (target, key, value) {
  7. invariant(key, 'set');
  8. target[key] = value;
  9. return true;
  10. }
  11. };
  12. function invariant (key, action) {
  13. if (key[0] === '_') {
  14. throw new Error( Invalid attempt to ${action} private "${key}" property );
  15. }
  16. }
  17. const target = {};
  18. const proxy = new Proxy(target, handler);
  19. proxy._prop
  20. // Error: Invalid attempt to get private "_prop" property
  21. proxy._prop = 'c'
  22. // Error: Invalid attempt to set private "_prop" property

In the above code, as long as the first character of the read-write property name is underlined, all thrown wrong, so as to achieve the purpose of prohibiting read-write internal properties.

The following is an example of the fourth parameter of the set method.

  1. const handler = {
  2. set: function(obj, prop, value, receiver) {
  3. obj[prop] = receiver;
  4. }
  5. };
  6. const proxy = new Proxy({}, handler);
  7. proxy.foo = 'bar';
  8. proxy.foo === proxy // true

In the above code, the fourth parameter of the set method, receiver, refers to the object where the original operation behavior is located, typically the proxy instance itself, see the following example.

  1. const handler = {
  2. set: function(obj, prop, value, receiver) {
  3. obj[prop] = receiver;
  4. }
  5. };
  6. const proxy = new Proxy({}, handler);
  7. const myObj = {};
  8. Object.setPrototypeOf(myObj, proxy);
  9. myObj.foo = 'bar';
  10. myObj.foo === myObj // true

In the code above, when setting the value of the myObj.foo property, myObj does not have a foo property, so the engine d'It doesn't look for the foo property in myObj's prototype chain. M yObj's prototype object proxy is a Proxy instance, and setting its foo property triggers the set method. At this point, the fourth parameter, receiver, points to the object myObj, where the original assignment behavior is located.

Note that if a property of the target object itself is not writeable and non-configurable, the set method will not work.

  1. const obj = {};
  2. Object.defineProperty(obj, 'foo', {
  3. value: 'bar',
  4. writable: false,
  5. });
  6. const handler = {
  7. set: function(obj, prop, value, receiver) {
  8. obj[prop] = 'baz';
  9. }
  10. };
  11. const proxy = new Proxy(obj, handler);
  12. proxy.foo = 'baz';
  13. proxy.foo // "bar"

In the code above, the obj.foo property is not writeable and Proxy's set proxy for this property will not take effect.

Note that in strict mode, the set agent will report an error if it does not return true.

  1. 'use strict';
  2. const handler = {
  3. set: function(obj, prop, value, receiver) {
  4. obj[prop] = receiver;
  5. // 无论有没有下面这一行,都会报错
  6. return false;
  7. }
  8. };
  9. const proxy = new Proxy({}, handler);
  10. proxy.foo = 'bar';
  11. // TypeError: 'set' on proxy: trap returned falsish for property 'foo'

In the above code, in strict mode, the set agent returns false or undefined, which will report an error.

apply()

apply method intercepts the call, call, and apply operations of the function.

apply method can accept three parameters, namely, 目标对象 the context object of 上下文对象 object (this), and the parameter array of the 参数数组

  1. var handler = {
  2. apply (target, ctx, args) {
  3. return Reflect.apply(...arguments);
  4. }
  5. };

Here's an example.

  1. var target = function () { return 'I am the target'; };
  2. var handler = {
  3. apply: function () {
  4. return 'I am the proxy';
  5. }
  6. };
  7. var p = new Proxy(target, handler);
  8. p()
  9. // "I am the proxy"

In the above code, the variable p is an instance of Proxy, and when it is called as a function (p(), it is intercepted by the apply method, returning a string.

Here's another example.

  1. var twice = {
  2. apply (target, ctx, args) {
  3. return Reflect.apply(...arguments) * 2;
  4. }
  5. };
  6. function sum (left, right) {
  7. return left + right;
  8. };
  9. var proxy = new Proxy(sum, twice);
  10. proxy(1, 2) // 6
  11. proxy.call(null, 5, 6) // 22
  12. proxy.apply(null, [7, 8]) // 30

In the code above, whenever a proxy function (called directly or call and apply) is executed, it is blocked by the apply method.

In addition, the Reflect.apply method is called directly and is blocked.

  1. Reflect.apply(proxy, null, [9, 10]) // 38

has()

has method is used HasProperty operations, which take effect when determining whether an object has a property. A typical operation is the in operator.

The has method can accept two parameters, the target 目标对象 需查询的属性名

The following example uses the has method to hide certain properties that are not discovered by the in operator.

  1. var handler = {
  2. has (target, key) {
  3. if (key[0] === '_') {
  4. return false;
  5. }
  6. return key in target;
  7. }
  8. };
  9. var target = { _prop: 'foo', prop: 'foo' };
  10. var proxy = new Proxy(target, handler);
  11. '_prop' in proxy // false

In the above code, if the first character of the property name of the original object is underlined, proxy.has returns false so that it is not discovered by the in operator.

If the original object is not configurable or can't be extended, has blocking will report an error.

  1. var obj = { a: 10 };
  2. Object.preventExtensions(obj);
  3. var p = new Proxy(obj, {
  4. has: function(target, prop) {
  5. return false;
  6. }
  7. });
  8. 'a' in p // TypeError is thrown

In the code above, the obj object prohibits extensions, and the result is an error by using has blocking. That is, if a property is not configurable (or the target object is not extensible), the has method must not "hide" (that is, return false) the property of the target object.

It is important to note that the has method intercepts the HasProperty operation, not the HasOwnProperty operation, which means that the has method does not determine whether a property is a property of the object itself or an inherited property.

Also, although for... T he in loop also uses the in operator, but has intercepts the pair for... The in loop does not take effect.

  1. let stu1 = {name: '张三', score: 59};
  2. let stu2 = {name: '李四', score: 99};
  3. let handler = {
  4. has(target, prop) {
  5. if (prop === 'score' && target[prop] < 60) {
  6. console.log( ${target.name} 不及格 );
  7. return false;
  8. }
  9. return prop in target;
  10. }
  11. }
  12. let oproxy1 = new Proxy(stu1, handler);
  13. let oproxy2 = new Proxy(stu2, handler);
  14. 'score' in oproxy1
  15. // 张三 不及格
  16. // false
  17. 'score' in oproxy2
  18. // true
  19. for (let a in oproxy1) {
  20. console.log(oproxy1[a]);
  21. }
  22. // 张三
  23. // 59
  24. for (let b in oproxy2) {
  25. console.log(oproxy2[b]);
  26. }
  27. // 李四
  28. // 99

In the above code, has blocking only takes effect on the in operator, on the for... T he in loop does not take effect, resulting in non-compliant properties not being for... The in loop is excluded.

construct()

construct method is used to intercept new and here's how to intercept objects.

  1. var handler = {
  2. construct (target, args, newTarget) {
  3. return new target(...args);
  4. }
  5. };

The construct method can accept three parameters.

  • Target: The target object
  • args: The argument object of the constructor
  • newTarget: When creating an instance object, the new command acts as a constructor (p in the following example)

  1. var p = new Proxy(function () {}, {
  2. construct: function(target, args) {
  3. console.log('called: ' + args.join(', '));
  4. return { value: args[0] * 10 };
  5. }
  6. });
  7. (new p(1)).value
  8. // "called: 1"
  9. // 10

construct method must return an object 对象 an error will be reported.

  1. var p = new Proxy(function() {}, {
  2. construct: function(target, argumentsList) {
  3. return 1;
  4. }
  5. });
  6. new p() // 报错
  7. // Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')

deleteProperty()

deleteProperty method is used to delete operations, and if this method throws an error or returns false, the current property cannot be deleted by the delete command.

  1. var handler = {
  2. deleteProperty (target, key) {
  3. invariant(key, 'delete');
  4. delete target[key];
  5. return true;
  6. }
  7. };
  8. function invariant (key, action) {
  9. if (key[0] === '_') {
  10. throw new Error( Invalid attempt to ${action} private "${key}" property );
  11. }
  12. }
  13. var target = { _prop: 'foo' };
  14. var proxy = new Proxy(target, handler);
  15. delete proxy._prop
  16. // Error: Invalid attempt to delete private "_prop" property

In the code above, the deleteProperty method intercepts the delete operator, and removing the underlined property of the first character will report an error.

Note that the target object's own non-configurable properties cannot be deleted by the deleteProperty method, otherwise an error is reported.

defineProperty()

defineProperty() method Object.defineProperty()

  1. var handler = {
  2. defineProperty (target, key, descriptor) {
  3. return false;
  4. }
  5. };
  6. var target = {};
  7. var proxy = new Proxy(target, handler);
  8. proxy.foo = 'bar' // 不会生效

In the code above, there is nothing inside the defineProperty() method that returns only false, causing the addition of new properties to always be invalid. Note that the false here is only used to prompt that the operation has failed and does not in itself prevent the addition of new properties.

Note that if the target object is not extensible, defineProperty() cannot add properties that do not exist on the target object, otherwise an error will be reported. Also, if a property of the target object is not written or configurable, the defineProperty() method must not change either setting.

getOwnPropertyDescriptor()

getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor() and returns a property description object or undefined.

  1. var handler = {
  2. getOwnPropertyDescriptor (target, key) {
  3. if (key[0] === '_') {
  4. return;
  5. }
  6. return Object.getOwnPropertyDescriptor(target, key);
  7. }
  8. };
  9. var target = { _foo: 'bar', baz: 'tar' };
  10. var proxy = new Proxy(target, handler);
  11. Object.getOwnPropertyDescriptor(proxy, 'wat')
  12. // undefined
  13. Object.getOwnPropertyDescriptor(proxy, '_foo')
  14. // undefined
  15. Object.getOwnPropertyDescriptor(proxy, 'baz')
  16. // { value: 'tar', writable: true, enumerable: true, configurable: true }

In the above code, the handler.getOwnPropertyDescriptor() method returns an underfined property name for the first character that is underlined.

getPrototypeOf()

getPrototypeOf() primarily used to intercept 获取对象原型 Specifically, block these operations below.

  • Object.prototype. proto
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • instanceof

Here's an example.

  1. var proto = {};
  2. var p = new Proxy({}, {
  3. getPrototypeOf(target) {
  4. return proto;
  5. }
  6. });
  7. Object.getPrototypeOf(p) === proto // true

In the code above, the getPrototypeOf() method intercepts Object.getPrototypeOf() and returns the proto object.

Note that getPrototypeOf() method must be 对象 or null otherwise an error is reported. In addition, if the target object is not extensible, the getPrototypeOf() method must return the prototype object of the target object.

isExtensible()

isExtensible() method Object.isExtensible()

  1. var p = new Proxy({}, {
  2. isExtensible: function(target) {
  3. console.log("called");
  4. return true;
  5. }
  6. });
  7. Object.isExtensible(p)
  8. // "called"
  9. // true

The above code sets the isExtensible() method, which outputs called when Object.isExtensible is called.

Note that the method can only return boolean values, otherwise the return values are automatically converted to Boolean values.

This method has a strong limitation, and its return value must be consistent with the isExtensible property of the target object, or an error will be thrown.

  1. Object.isExtensible(proxy) === Object.isExtensible(target)

Here's an example.

  1. var p = new Proxy({}, {
  2. isExtensible: function(target) {
  3. return false;
  4. }
  5. });
  6. Object.isExtensible(p)
  7. // Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')

ownKeys()

The ownKeys() method is used to intercept read operations on the object's own properties. Specifically, block the following operations.

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for... in loop

The following is an example of intercepting Object.keys().

  1. let target = {
  2. a: 1,
  3. b: 2,
  4. c: 3
  5. };
  6. let handler = {
  7. ownKeys(target) {
  8. return ['a'];
  9. }
  10. };
  11. let proxy = new Proxy(target, handler);
  12. Object.keys(proxy)
  13. // [ 'a' ]

The above code intercepts the Object.keys() operation on the target object, returning only the a property of a, b, and c of the three properties.

The following example is intercepting a property name where the first character is underlined.

  1. let target = {
  2. _bar: 'foo',
  3. _prop: 'bar',
  4. prop: 'baz'
  5. };
  6. let handler = {
  7. ownKeys (target) {
  8. return Reflect.ownKeys(target).filter(key => key[0] !== '_');
  9. }
  10. };
  11. let proxy = new Proxy(target, handler);
  12. for (let key of Object.keys(proxy)) {
  13. console.log(target[key]);
  14. }
  15. // "baz"

Note that when using the Object.keys() method, three types of properties are automatically filtered by the ownKeys() method and are not returned.

  • Properties that do not exist on the target object
  • The property name is symbol value
  • Properties that cannot be traversed (enumerable).

  1. let target = {
  2. a: 1,
  3. b: 2,
  4. c: 3,
  5. [Symbol.for('secret')]: '4',
  6. };
  7. Object.defineProperty(target, 'key', {
  8. enumerable: false,
  9. configurable: true,
  10. writable: true,
  11. value: 'static'
  12. });
  13. let handler = {
  14. ownKeys(target) {
  15. return ['a', 'd', Symbol.for('secret'), 'key'];
  16. }
  17. };
  18. let proxy = new Proxy(target, handler);
  19. Object.keys(proxy)
  20. // ['a']

In the above code, in the ownKeys() method, the return of non-existent properties (d), Symbol values (Symbol.for('secret'), and non-traversable properties (key) are displayed, and the results are automatically filtered out.

The ownKeys() method can also intercept Object.getOwnPropertyNames().

  1. var p = new Proxy({}, {
  2. ownKeys: function(target) {
  3. return ['a', 'b', 'c'];
  4. }
  5. });
  6. Object.getOwnPropertyNames(p)
  7. // [ 'a', 'b', 'c' ]

for... The in loop is also blocked by the ownKeys() method.

  1. const obj = { hello: 'world' };
  2. const proxy = new Proxy(obj, {
  3. ownKeys: function () {
  4. return ['a', 'b'];
  5. }
  6. });
  7. for (let key in proxy) {
  8. console.log(key); // 没有任何输出
  9. }

In the above code, ownkeys() specifies that only the a and b properties are returned, and because obj does not have these two properties, for... The in loop does not have any output.

The ownKeys() method returns array members that can only be strings or Symbol values. If there are other types of values, or if the return is not an array at all, an error is reported.

  1. var obj = {};
  2. var p = new Proxy(obj, {
  3. ownKeys: function(target) {
  4. return [123, true, undefined, null, {}, []];
  5. }
  6. });
  7. Object.getOwnPropertyNames(p)
  8. // Uncaught TypeError: 123 is not a valid property name

In the code above, the ownKeys() method returns an array, but each array member is not a string or Symbol value, so it is misaled.

If the target object itself contains an unconfigurable property, the property must be returned by the ownKeys() method or an error will be reported.

  1. var obj = {};
  2. Object.defineProperty(obj, 'a', {
  3. configurable: false,
  4. enumerable: true,
  5. value: 10 }
  6. );
  7. var p = new Proxy(obj, {
  8. ownKeys: function(target) {
  9. return ['b'];
  10. }
  11. });
  12. Object.getOwnPropertyNames(p)
  13. // Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'a'

In the code above, the a property of the obj object is not configurable, and the array returned by the ownKeys() method must contain a or an error will be reported.

Also, if the target object is not extensible, the array returned by the ownKeys() method must contain all the properties of the original object and must not contain extra properties, otherwise an error is reported.

  1. var obj = {
  2. a: 1
  3. };
  4. Object.preventExtensions(obj);
  5. var p = new Proxy(obj, {
  6. ownKeys: function(target) {
  7. return ['a', 'b'];
  8. }
  9. });
  10. Object.getOwnPropertyNames(p)
  11. // Uncaught TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible

In the above code, the obj object is not extensable, and the array returned by the ownKeys() method contains the excess properties of the obj object b, which results in an error.

preventExtensions()

preventExtensions() method Object.preventExtensions() The method must return a Boolean value 布尔值 will automatically be converted to a Boolean value.

This method has a limitation that only when the target object is not scalable (i.e. Object.isExtensible (proxy) is false), proxy.preventExtensions can return true, otherwise an error will be reported.

  1. var proxy = new Proxy({}, {
  2. preventExtensions: function(target) {
  3. return true;
  4. }
  5. });
  6. Object.preventExtensions(proxy)
  7. // Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible

In the code above, the proxy.preventExtensions() method returns true, but object.isExtensible (proxy) returns true and therefore errors are reported.

To prevent this problem, object.preventExtensions() is usually called once in the proxy.preventExtensions() method.

  1. var proxy = new Proxy({}, {
  2. preventExtensions: function(target) {
  3. console.log('called');
  4. Object.preventExtensions(target);
  5. return true;
  6. }
  7. });
  8. Object.preventExtensions(proxy)
  9. // "called"
  10. // Proxy {}

setPrototypeOf()

setPrototypeOf() is mainly Object.setPrototypeOf() method.

Here's an example.

  1. var handler = {
  2. setPrototypeOf (target, proto) {
  3. throw new Error('Changing the prototype is forbidden');
  4. }
  5. };
  6. var proto = {};
  7. var target = function () {};
  8. var proxy = new Proxy(target, handler);
  9. Object.setPrototypeOf(proxy, proto);
  10. // Error: Changing the prototype is forbidden

In the code above, any modification of the target's prototype object will result in an error.

Note that the method can only return boolean values, otherwise they will be automatically converted to Boolean values. In addition, if the target object is not extensible, the setPrototypeOf() method must not change the prototype of the target object.

3. Proxy.revocable()

Proxy.revocable() method returns an instance of the Proxy that can Proxy

  1. let target = {};
  2. let handler = {};
  3. let {proxy, revoke} = Proxy.revocable(target, handler);
  4. proxy.foo = 123;
  5. proxy.foo // 123
  6. revoke();
  7. proxy.foo // TypeError: Revoked

The Proxy.revocable() method returns an object, the proxy property of which is a Proxy instance, and the revoke property is a function that cancels the Proxy instance. In the code above, an error is thrown when the revoke function is executed and the Proxy instance is accessed.

One use scenario for Proxy.revocable() is that the target object is not allowed direct access, must be accessed through a proxy, and once the access is complete, the proxy is withdrawn and no further access is allowed.

4. This problem

Although Proxy can proxy access to the target object, it is not a transparent proxy for the target object, i.e. it cannot guarantee that it will behave consistently with the target object without any interception. The main reason is that in the case of the Proxy proxy, the this keyword inside the target object points to the Proxy proxy.

  1. const target = {
  2. m: function () {
  3. console.log(this === proxy);
  4. }
  5. };
  6. const handler = {};
  7. const proxy = new Proxy(target, handler);
  8. target.m() // false
  9. proxy.m() // true

In the above code, once the proxy proxy target .m, the this inside the latter points to the proxy, not the target.

Here's an example of a change in this pointing that prevents Proxy from acting as a target object.

  1. const _name = new WeakMap();
  2. class Person {
  3. constructor(name) {
  4. _name.set(this, name);
  5. }
  6. get name() {
  7. return _name.get(this);
  8. }
  9. }
  10. const jane = new Person('Jane');
  11. jane.name // 'Jane'
  12. const proxy = new Proxy(jane, {});
  13. proxy.name // undefined

In the code above, the name property of the target object jane is actually saved on top of the external WeakMap object _name, distinguished by the this key. Because this points proxy.name proxy when accessed through the data, the value cannot be obtained, so the undefined is returned.

In addition, some of the internal properties of native objects can only be obtained through the correct this, so Proxy cannot represent the properties of these native objects.

  1. const target = new Date();
  2. const handler = {};
  3. const proxy = new Proxy(target, handler);
  4. proxy.getDate();
  5. // TypeError: this is not a Date object.

In the code above, the getDate() method can only be obtained on top of the Date object instance, and if this is not a Date object instance, an error will be reported. At this point, this binds the original object and solves the problem.

  1. const target = new Date('2015-01-01');
  2. const handler = {
  3. get(target, prop) {
  4. if (prop === 'getDate') {
  5. return target.getDate.bind(target);
  6. }
  7. return Reflect.get(target, prop);
  8. }
  9. };
  10. const proxy = new Proxy(target, handler);
  11. proxy.getDate() // 1

5. Instance: The client of the Web service

Proxy object can intercept any property of the target object, which makes it a good fit for clients that write Web services.

  1. const service = createWebService('http://example.com/data');
  2. service.employees().then(json => {
  3. const employees = JSON.parse(json);
  4. // ···
  5. });

The code above creates a new interface for a Web service that returns a variety of data. Proxy can intercept any property of this object, so you don't have to write an appropriate method for each type of data, just write a Proxy intercept.

  1. function createWebService(baseUrl) {
  2. return new Proxy({}, {
  3. get(target, propKey, receiver) {
  4. return () => httpGet(baseUrl + '/' + propKey);
  5. }
  6. });
  7. }

Similarly, Proxy can also be used to implement the ORM layer of the database.