This is the 17th day of my participation in the August Text Challenge.More challenges in August

An overview,

Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level. Therefore, it is a kind of “meta programming”, that is, programming a programming language.

Proxy can be understood as a layer of “interception” before the target object. All external access to the object must pass this layer of interception. Therefore, Proxy provides a mechanism for filtering and rewriting external access. The word Proxy is used to mean that it acts as a Proxy for certain operations.

var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}! `);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}! `);
    return Reflect.set(target, propKey, value, receiver); }});Copy the code

The code above sets up a layer of interception on an empty object, redefining the read (get) and set (set) behavior of the property. I won’t explain the syntax here, but just look at the results. To read and write properties of obj, the object with interception behavior set, the following result is obtained.

obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
//  2
Copy the code

The code above shows that the Proxy actually overrides the point operator by overwriting the original definition of the language with its own definition.

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

var proxy = new Proxy(target, handler);
Copy the code

All uses of Proxy objects are in this form, except for the handler arguments. The new Proxy() parameter represents the generation of a Proxy instance, the target parameter represents the target object to intercept, and the handler parameter is also an object used to customize the interception behavior.

Here is another example of intercepting the behavior of reading properties.

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35; }}); proxy.time/ / 35
proxy.name / / 35
proxy.title / / 35
Copy the code

In the code above, Proxy takes two arguments as a constructor.

The first parameter is the target object (an empty object in this example) that the operation would have accessed without Proxy intervention.

The second argument is a configuration object that needs to provide a corresponding handler function for each propped operation that intercepts the corresponding operation. For example, in the code above, the configuration object has a GET method that intercepts access requests to properties of the target object.

The two arguments to the GET method are the target object and the property to be accessed. As you can see, since the interceptor always returns 35, accessing any property yields 35.

Note that for the Proxy to work, you must operate on the Proxy instance (the Proxy object in this example), not the target object (the empty object in this example).

If the handler does not set up any interceptions, that is equivalent to going straight to the original object.

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
Copy the code

In the code above, handler is an empty object with no interception 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 object objects.

var object = { proxy: new Proxy(target, handler) };
Copy the code

Proxy instances can also serve as prototype objects for other objects.

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35; }});let obj = Object.create(proxy);
obj.time / / 35
Copy the code

In the above code, the proxy object is the prototype of the OBJ object. The OBJ object itself does not have the time attribute, so according to the prototype chain, the proxy object will read this attribute, resulting in interception.

The same interceptor function can be set to intercept more than one operation.

var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]}; }};var fproxy = new Proxy(function(x, y) {
  return x + y;
}, handler);

fproxy(1.2) / / 1
new fproxy(1.2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
Copy the code

For actions that can be set, but no interception is set, they fall directly on the target object and produce the result as before.

The following is a list of 13 interception operations supported by Proxy.

  • get(target, propKey, receiver): intercepts the reading of object properties, such asproxy.fooproxy['foo'].
  • set(target, propKey, value, receiver): Intercepts the setting of object properties, such asproxy.foo = vproxy['foo'] = v, returns a Boolean value.
  • has(target, propKey)Intercept:propKey in proxyReturns a Boolean value.
  • deleteProperty(target, propKey)Intercept:delete proxy[propKey]Returns a Boolean value.
  • ownKeys(target)Intercept:Object.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy),for... inLoop to return an array. This method returns the property names of all of the target object’s own properties, andObject.keys()The return result of the object contains only the traversable properties of the target object itself.
  • getOwnPropertyDescriptor(target, propKey)Intercept:Object.getOwnPropertyDescriptor(proxy, propKey)Returns the description object of the property.
  • defineProperty(target, propKey, propDesc)Intercept:Object.defineProperty(proxy, propKey, propDesc),Object.defineProperties(proxy, propDescs), returns a Boolean value.
  • preventExtensions(target)Intercept:Object.preventExtensions(proxy), returns a Boolean value.
  • getPrototypeOf(target)Intercept:Object.getPrototypeOf(proxy), returns an object.
  • isExtensible(target)Intercept:Object.isExtensible(proxy), returns a Boolean value.
  • setPrototypeOf(target, proto)Intercept:Object.setPrototypeOf(proxy, proto), returns a Boolean value. If the target object is a function, there are two additional operations that can be intercepted.
  • apply(target, object, args): Intercepts operations called by Proxy instances as functions, such asproxy(... args),proxy.call(object, ... args),proxy.apply(...).
  • construct(target, args): intercepts operations called by Proxy instances as constructors, such asnew proxy(... args).

Method of Proxy instance

Below is a detailed description of the above interception methods.

1. get()

The get method intercepts a read of a property and can take three parameters, the target object, the property name, and the Proxy instance itself (strictly speaking, the object against which the action is directed), the last of which is optional.

Here is another example of how to intercept a read operation.

var person = {
  name: "Zhang"
};

var proxy = new Proxy(person, {
  get: function(target, propKey) {
    if (propKey in target) {
      return target[propKey];
    } else {
      throw new ReferenceError("Prop name \"" + propKey + "\" does not exist."); }}}); proxy.name// "/"
proxy.age // Throw an error
Copy the code

The code above shows that an error is thrown if an attribute of the target object is accessed that does not exist. Without this interceptor, access to nonexistent properties will only return undefined.

The GET method can be inherited.

let proto = new Proxy({}, {
  get(target, propertyKey, receiver) {
    console.log('GET ' + propertyKey);
    returntarget[propertyKey]; }});let obj = Object.create(proto);
obj.foo // "GET foo"
Copy the code

In the code above, the interception is defined on the Prototype object, so if an obj object inherits a property, the interception takes effect.

The following example uses get intercepts to read indexes of negative numbers in an array.

function createArray(. elements) {
  let handler = {
    get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
        propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver); }};lettarget = []; target.push(... elements);return new Proxy(target, handler);
}

let arr = createArray('a'.'b'.'c');
arr[-1] // c
Copy the code

In the above code, the position argument of the array is -1, which prints the first inverse member of the array.

Using Proxy, you can convert the operation of reading a property (GET) into the execution of a function to achieve the chain operation of the property.

var pipe = function (value) {
  var funcStack = [];
  var oproxy = new Proxy({}, {get : function (pipeObject, fnName) {
      if (fnName === 'get') {
        return funcStack.reduce(function (val, fn) {
          return fn(val);
        },value);
      }
      funcStack.push(window[fnName]);
      returnoproxy; }});return oproxy;
}

var double = n= > n * 2;
var pow    = n= > n * n;
var reverseInt = n= > n.toString().split("").reverse().join("") | 0;

pipe(3).double.pow.reverseInt.get; / / 63
Copy the code

After setting Proxy in the code above, the function name is chained.

The following example uses get interception to implement a generic DOM function that generates various DOM nodes.

const dom = new Proxy({}, {
  get(target, property) {
    return function(attrs = {}, ... children) {
      const el = document.createElement(property);
      for (let prop of Object.keys(attrs)) {
        el.setAttribute(prop, attrs[prop]);
      }
      for (let child of children) {
        if (typeof child === 'string') {
          child = document.createTextNode(child);
        }
        el.appendChild(child);
      }
      returnel; }}});const el = dom.div({},
  'Hello, my name is ',
  dom.a({href: '//example.com'}, 'Mark'),
  '. I like:',
  dom.ul({},
    dom.li({}, 'The web'),
    dom.li({}, 'Food'),
    dom.li({}, '... actually that\'s it')));document.body.appendChild(el);
Copy the code

Here is an example of the third argument to the get method, which always points to the object where the original read operation was performed, typically a Proxy instance.

const proxy = new Proxy({}, {
  get: function(target, key, receiver) {
    returnreceiver; }}); proxy.getReceiver === proxy// true
Copy the code

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

const proxy = new Proxy({}, {
  get: function(target, key, receiver) {
    returnreceiver; }});const d = Object.create(proxy);
d.a === d // true
Copy the code

In the above code, d object itself has no a attribute, so when reading d.a, it will look for the prototype proxy object of d. The receiver then points to D, representing the object where the original read operation was performed.

If a property cannot be configured and writable, the Proxy cannot modify the property. Otherwise, an error will be reported when accessing the property through the Proxy object.

const target = Object.defineProperties({}, {
  foo: {
    value: 123.writable: false.configurable: false}});const handler = {
  get(target, propKey) {
    return 'abc'; }};const proxy = new Proxy(target, handler);

proxy.foo
// TypeError: Invariant check failed
Copy the code

2. set()

The set method is used to intercept the assignment of an attribute. It can take four parameters: the target object, the attribute name, the attribute value, and the Proxy instance itself. The last parameter is optional.

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

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 the age attribute and other attributes that meet the criteriaobj[prop] = value; }};let person = new Proxy({}, validator);

person.age = 100;

person.age / / 100
person.age = 'young' / / an error
person.age = 300 / / an error
Copy the code

In the code above, due to the set function, any improper assignment of the age property will throw an error, which is one implementation of data validation. With the set method, you can also use data binding, which automatically updates the DOM whenever an object changes.

Sometimes we set internal properties on objects, with the first character of the property name beginning with an underscore to indicate that these properties should not be used externally. The combination of the GET and set methods prevents these internal attributes from being read or written externally.

const handler = {
  get (target, key) {
    invariant(key, 'get');
    return target[key];
  },
  set (target, key, value) {
    invariant(key, 'set');
    target[key] = value;
    return true; }};function invariant (key, action) {
  if (key[0= = ='_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`); }}const target = {};
const proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
Copy the code

In the above code, as long as the first character of the read and write attribute name is underscore, all throw error, so as to achieve the purpose of reading and writing internal attribute.

Here is an example of the fourth argument to the set method.

const handler = {
  set: function(obj, prop, value, receiver) { obj[prop] = receiver; }};const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
proxy.foo === proxy // true
Copy the code

In the above code, the fourth parameter of the set method, receiver, refers to the object where the original action is, usually the proxy instance itself, as shown in the following example.

const handler = {
  set: function(obj, prop, value, receiver) { obj[prop] = receiver; }};const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);

myObj.foo = 'bar';
myObj.foo === myObj // true
Copy the code

In the code above, when you set the value of myObj. Foo, myObj doesn’t have foo, so the engine looks for foo in myObj’s prototype chain. MyObj’s prototype object proxy is a Proxy instance whose property foo triggers the set method. The fourth parameter, receiver, points to the object myObj where the original assignment occurred.

Note that the set method does not work if a property of the target object itself is not writable or configurable.

const obj = {};
Object.defineProperty(obj, 'foo', {
  value: 'bar'.writable: false});const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = 'baz'; }};const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"
Copy the code

In the code above, the obj.foo property is not writable, and the Proxy 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.

'use strict';
const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
    // An error is reported with or without the following line
    return false; }};const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'
Copy the code

In strict mode, the set agent returns false or undefined.

3. apply()

The Apply method intercepts function calls, calls, and apply operations.

The Apply method takes three parameters: the target object, the target object’s context object (this), and the target object’s parameter array.

var handler = {
  apply (target, ctx, args) {
    return Reflect.apply(...arguments);
  }
};
Copy the code

Here’s an example.

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy'; }};var p = new Proxy(target, handler);

p()
// "I am the proxy"
Copy the code

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

Here’s another example.

var twice = {
  apply (target, ctx, args) {
    return Reflect.apply(... arguments) *2; }};function sum (left, right) {
  return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1.2) / / 6
proxy.call(null.5.6) / / 22
proxy.apply(null[7.8]) / / 30
Copy the code

In the above code, whenever a proxy function is executed (either a direct call or a call and apply call), it is intercepted by the Apply method.

Alternatively, calling reflect. apply directly will also be intercepted.

Reflect.apply(proxy, null[9.10]) / / 38
Copy the code

4.has()

The has() method is used to intercept the HasProperty operation, which takes effect when determining whether an object has a property. A typical operation is the IN operator.

The has() method can take two parameters, the target object and the name of the property to be queried.

The following example uses the has() method to hide some attributes from the IN operator.

var handler = {
  has (target, key) {
    if (key[0= = ='_') {
      return false;
    }
    return key intarget; }};var target = { _prop: 'foo'.prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
Copy the code

In the above code, proxy.has() returns false if the first character of the property name of the original object is an underscore, thus avoiding detection by the IN operator.

Has () intercepts an error if the original object is not configurable or if extension is disabled.

var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
  has: function(target, prop) {
    return false; }});'a' in p // TypeError is thrown
Copy the code

In the above code, the obj object disables extension, resulting in an error using has interception. 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, meaning that the HAS () method does not determine whether a property is a property of the object itself or an inherited property.

In addition, although… The in loop also uses the in operator, but has() intercepts for… The in loop does not take effect.

let stu1 = {name: 'Joe'.score: 59};
let stu2 = {name: 'bill'.score: 99};

let handler = {
  has(target, prop) {
    if (prop === 'score' && target[prop] < 60) {
      console.log(`${target.name}Fail `);
      return false;
    }
    return prop intarget; }}let oproxy1 = new Proxy(stu1, handler);
let oproxy2 = new Proxy(stu2, handler);

'score' in oproxy1
// Zhang SAN failed
// false

'score' in oproxy2
// true

for (let a in oproxy1) {
  console.log(oproxy1[a]);
}
/ / zhang SAN
/ / 59

for (let b in oproxy2) {
  console.log(oproxy2[b]);
}
/ / li si
/ / 99
Copy the code

In the code above, the has() intercept works only for the in operator, for… The in loop does not work, resulting in unqualified attributes not being for… Excluded by the in loop.

5. construct()

The construct() method is used to intercept the new command. Here’s how to write the intercept object.

const handler = {
  construct (target, args, newTarget) {
    return new target(...args);
  }
};
Copy the code

The construct() method takes three arguments.

  • target: Target object.
  • args: Array of constructor arguments.
  • newTarget: When creating instance objects,newThe constructor used by the command (in the following example)p).
const p = new Proxy(function () {}, {
  construct: function(target, args) {
    console.log('called: ' + args.join(', '));
    return { value: args[0] * 10}; }}); (new p(1)).value
// "called: 1"
/ / 10
Copy the code

The construct() method must return an object or an error will be reported.

const p = new Proxy(function() {}, {
  construct: function(target, argumentsList) {
    return 1; }});new p() / / an error
// Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')
Copy the code

Also, since construct() intercepts a constructor, its target object must be a function or an error will be reported.

const p = new Proxy({}, {
  construct: function(target, argumentsList) {
    return{}; }});new p() / / an error
// Uncaught TypeError: p is not a constructor
Copy the code

In the example above, the object being intercepted is not a function, but an object (the first argument to new Proxy()), resulting in an error.

Notice that this in the construct() method points to handler, not the instance object.

const handler = {
  construct: function(target, args) {
    console.log(this ===  handler);
    return new target(...args);
  }
}

let p = new Proxy(function () {}, handler);
new p() // true
Copy the code

6. deleteProperty()

The deleteProperty method intercepts the DELETE operation. If the method throws an error or returns false, the current property cannot be deleted by the delete command.

var handler = {
  deleteProperty (target, key) {
    invariant(key, 'delete');
    delete target[key];
    return true; }};function invariant (key, action) {
  if (key[0= = ='_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`); }}var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property
Copy the code

In the code above, the deleteProperty method intercepts the delete operator, and deleting an attribute whose first character is an underscore raises an error.

The property of the target object, which cannot be configured, cannot be deleted by the deleteProperty method. Otherwise, an error is reported.

7. defineProperty()

The defineProperty() method intercepts the Object.defineProperty() operation.

var handler = {
  defineProperty (target, key, descriptor) {
    return false; }};var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar' // Does not take effect
Copy the code

In the above code, the defineProperty() method does nothing inside but returns false, so adding new attributes is always invalid. Note that false is used only to indicate that the operation failed and does not in itself prevent new properties from being added.

Note that defineProperty() cannot add attributes that do not exist on the target object if the target object is non-extensible, or an error is reported. In addition, if a property of the target object is not writable or configurable, the defineProperty() method cannot change the two Settings.

8. getOwnPropertyDescriptor()

GetOwnPropertyDescriptor intercept () method of the Object. GetOwnPropertyDescriptor (), returns a property description Object, or undefined.

var handler = {
  getOwnPropertyDescriptor (target, key) {
    if (key[0= = ='_') {
      return;
    }
    return Object.getOwnPropertyDescriptor(target, key); }};var target = { _foo: 'bar'.baz: 'tar' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat')
// undefined
Object.getOwnPropertyDescriptor(proxy, '_foo')
// undefined
Object.getOwnPropertyDescriptor(proxy, 'baz')
// { value: 'tar', writable: true, enumerable: true, configurable: true }
Copy the code

The above code, the handler. GetOwnPropertyDescriptor () method for the first character to underline the property name will return undefined.

9. getPrototypeOf()

The getPrototypeOf() method is used primarily to intercept and retrieve object prototypes. Specifically, intercept the following operations.

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

Here’s an example.

var proto = {};
var p = new Proxy({}, {
  getPrototypeOf(target) {
    returnproto; }});Object.getPrototypeOf(p) === proto // true
Copy the code

In the code above, the getPrototypeOf() method intercepts Object.getProtoTypeof () and returns a Proto Object.

Note that the return value of the getPrototypeOf() method must be an object or null, otherwise an error is reported. Also, if the target object is non-extensible, the getPrototypeOf() method must return the prototype object of the target object.

10. isExtensible()

The isExtensible() method intercepts the Object.isextensible () operation.

var p = new Proxy({}, {
  isExtensible: function(target) {
    console.log("called");
    return true; }});Object.isExtensible(p)
// "called"
// true
Copy the code

The above code sets up isExtensible(), which prints called when calling Object.isextensible.

Note that this method can only return a Boolean value, otherwise the return value is automatically converted to a Boolean value.

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

Object.isExtensible(proxy) === Object.isExtensible(target)
Copy the code

Here’s an example.

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

11. ownKeys()

The ownKeys() method is used to intercept reads of the object’s own properties. Specifically, intercept the following operations.

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for... incycle

Here is an example of intercepting Object.keys().

let target = {
  a: 1.b: 2.c: 3
};

let handler = {
  ownKeys(target) {
    return ['a']; }};let proxy = new Proxy(target, handler);

Object.keys(proxy)
// [ 'a' ]
Copy the code

This code intercepts the object.keys () operation on the target Object, returning only the a property of a, B, and C.

The following example intercepts attribute names whose first character is an underscore.

let target = {
  _bar: 'foo'._prop: 'bar'.prop: 'baz'
};

let handler = {
  ownKeys (target) {
    return Reflect.ownKeys(target).filter(key= > key[0]! = ='_'); }};let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
  console.log(target[key]);
}
// "baz"
Copy the code

Note that there are three types of attributes that ownKeys() automatically filters and does not return when using object.keys ().

  • Properties that do not exist on the target object
  • Properties,SymboL value
  • Non-traversal (enumerable) the properties of the
let target = {
  a: 1.b: 2.c: 3[Symbol.for('secret')]: '4'};Object.defineProperty(target, 'key', {
  enumerable: false.configurable: true.writable: true.value: 'static'
});

let handler = {
  ownKeys(target) {
    return ['a'.'d'.Symbol.for('secret'), 'key']; }};let proxy = new Proxy(target, handler);

Object.keys(proxy)
// ['a']
Copy the code

In the above code, ownKeys() method explicitly returns nonexistent property (d), Symbol value (symbol.for (‘secret’)), non-traversable property (key), results are automatically filtered.

OwnKeys () method can also intercept Object. GetOwnPropertyNames ().

var p = new Proxy({}, {
  ownKeys: function(target) {
    return ['a'.'b'.'c']; }});Object.getOwnPropertyNames(p)
// [ 'a', 'b', 'c' ]
for. The in loop is also intercepted by the ownKeys() method.const obj = { hello: 'world' };
const proxy = new Proxy(obj, {
  ownKeys: function () {
    return ['a'.'b']; }});for (let key in proxy) {
  console.log(key); // There is no output
}
Copy the code

In the code above, ownkeys() specifies that only a and B attributes are returned. The in loop has no output.

The ownKeys() method returns an array member that can only be a string or Symbol value. If there is another type of value, or if it is not an array at all, an error is reported.

var obj = {};

var p = new Proxy(obj, {
  ownKeys: function(target) {
    return [123.true.undefined.null, {}, []]; }});Object.getOwnPropertyNames(p)
// Uncaught TypeError: 123 is not a valid property name
Copy the code

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

If the target object itself contains a non-configurable property, that property must be returned by the ownKeys() method, otherwise an error is reported.

var obj = {};
Object.defineProperty(obj, 'a', {
  configurable: false.enumerable: true.value: 10});var p = new Proxy(obj, {
  ownKeys: function(target) {
    return ['b']; }});Object.getOwnPropertyNames(p)
// Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'a'
Copy the code

The ownKeys() method must return an array containing a, otherwise an error will be reported.

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

var obj = {
  a: 1
};

Object.preventExtensions(obj);

var p = new Proxy(obj, {
  ownKeys: function(target) {
    return ['a'.'b']; }});Object.getOwnPropertyNames(p)
// Uncaught TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible
Copy the code

The ownKeys() method returns an array containing the obj property b, which is not extensible.

12. preventExtensions()

The preventExtensions() method intercepts object.preventExtensions (). This method must return a Boolean value, otherwise it is automatically converted to a Boolean value.

The limit to this method is that proxy.preventExtensions can only return true if the target Object is not extensible (Object. IsExtensible (proxy) is false), otherwise an error will be reported.

var proxy = new Proxy({}, {
  preventExtensions: function(target) {
    return true; }});Object.preventExtensions(proxy)
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible
Copy the code

In the code above, proxy.preventExtensions() returns true, but object.isextensible (proxy) returns true, so an error is reported.

To prevent this, usually call Object.preventExtensions() once in the proxy.preventExtensions() method.

var proxy = new Proxy({}, {
  preventExtensions: function(target) {
    console.log('called');
    Object.preventExtensions(target);
    return true; }});Object.preventExtensions(proxy)
// "called"
// Proxy {}
Copy the code

13. setPrototypeOf()

The setPrototypeOf() method is mainly used to intercept object.setPrototypeof () methods.

Here’s an example.

var handler = {
  setPrototypeOf (target, proto) {
    throw new Error('Changing the prototype is forbidden'); }};var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden
Copy the code

In the above code, an error is reported whenever the target prototype object is modified.

Note that this method can only return a Boolean value, otherwise it is automatically converted to a Boolean value. Also, if the target object is non-extensible, the setPrototypeOf() method cannot change the target object’s prototype.

Third, Proxy. Revocable ()

The proxy.revocable () method returns a cancelable Proxy instance.

let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo / / 123

revoke();
proxy.foo // TypeError: Revoked
Copy the code

The proxy.revocable () method returns an object whose Proxy property is a Proxy instance. Revoke property is a function that cancels a Proxy instance. In the above code, when accessing the Proxy instance after executing revoke, an error is thrown.

One use scenario for proxy.revocable () is where the target object is not allowed to be accessed directly, but must be accessed through a Proxy, and once the access is over, the Proxy is revoked and no further access is allowed.

Fourth, this question

Normally, this in the Construct hook function points to the Proxy instance (except for the Construct hook function, where this points to handler).

Although Proxy can Proxy the access of the target object, it is not a transparent Proxy of the target object, that is, without any interception, it cannot guarantee the consistency with the behavior of the target object. The main reason is that in the case of Proxy, the this keyword inside the target object points to Proxy.

const target = {
  m: function () {
    console.log(this=== proxy); }};const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true
Copy the code

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

Here is an example where the Proxy is unable to Proxy the target object due to a change in the direction of this.

const _name = new WeakMap(a);class Person {
  constructor(name) {
    _name.set(this, name);
  }
  get name() {
    return _name.get(this); }}const jane = new Person('Jane');
jane.name // 'Jane'

const proxy = new Proxy(jane, {});
proxy.name // undefined
Copy the code

In the above code, the name attribute of the target object Jane is actually saved above the external WeakMap object _name, distinguished by this key. This cannot fetch the value because this refers to proxy when accessed through proxy.name. Therefore, undefined is returned.

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

const target = new Date(a);const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.
Copy the code

In the above code, the getDate() method is only available on the Date object instance, and an error is reported if this is not a Date object instance. This fixes the problem by binding the original object to this.

const target = new Date('2015-01-01');
const handler = {
  get(target, prop) {
    if (prop === 'getDate') {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, prop); }};const proxy = new Proxy(target, handler);

proxy.getDate() / / 1
Copy the code

Example: client of Web service

Proxy objects can intercept arbitrary properties of target objects, making them ideal for writing to clients of Web services.

const service = createWebService('http://example.com/data');

service.employees().then(json= > {
  const employees = JSON.parse(json);
  / /...
});
Copy the code

The code above creates a new Web service interface that returns various data. Proxy intercepts arbitrary properties of this object, so instead of writing an adaptation method for each type of data, just write a Proxy interceptor.

function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, propKey, receiver) {
      return () = > httpGet(baseUrl + '/'+ propKey); }}); }Copy the code

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