vue3 data response principle Proxy -- Ruan Yifeng

1. General

Proxy is used to modify the default behavior of some operations, which is equivalent to making modifications at the language level. Therefore, it belongs to a kind of "meta programming", that is, programming the programming language.

Proxy can be understood as setting up a layer of "interception" before the target object. External access to the object must first pass through this layer of interception. Therefore, it provides a mechanism to filter and rewrite external access. The original meaning of the word proxy is proxy. It is used here to "proxy" some operations, which can be translated as "proxy".

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);
  }
});

The above code sets up a layer of interception for an empty object and redefines the get and set behavior of attributes. For the time being, we will not explain the specific syntax, but just look at the running results. Read and write the properties of the object obj with interception behavior set, and you will get the following results.

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2

The above code shows that Proxy actually overload s the dot operator, that is, it overwrites the original definition of the language with its own definition.

ES6 native provides a Proxy constructor to generate Proxy instances.

var proxy = new Proxy(target, handler);

All uses of Proxy objects are in the above form, except for the writing of handler parameters. Among them, new Proxy() means to generate a Proxy instance, the target parameter means the target object to be intercepted, and the handler parameter is also an object to customize the interception behavior.

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

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

In the above code, as a constructor, Proxy accepts two parameters. The first parameter is the target object to be proxied (the above example is an empty object), that is, if there is no Proxy intervention, the original object to be accessed by the operation is this object; The second parameter is a configuration object. For each Proxy operation, you need to provide a corresponding processing function, which will intercept the corresponding operation. For example, in the above code, the configuration object has a get method, which is used to intercept access requests to target object properties. The two parameters of the get method are the target object and the property to be accessed. As you can see, since the interceptor function always returns 35, you will get 35 when accessing any attribute.

Note that to make proxy work, you must operate on the proxy instance (proxy object in the previous example), not on the target object (empty object in the previous example).

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

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

In the above code, the handler is an empty object without any interception effect. Accessing the proxy is equivalent to accessing the target.

One trick is to set the proxy object to object Proxy property, which can be called on the object object.

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

Proxy instances can also be used as prototype objects for other objects.

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

let obj = Object.create(proxy);
obj.time // 35

In the above code, the proxy object is the prototype of the obj object. The obj object itself has no time attribute. Therefore, according to the prototype chain, this attribute will be read on the proxy object, resulting in being intercepted.

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

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

For operations that can be set but do not set interception, they will directly fall on the target object and produce results in the original way.

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

  • get(target, propKey, receiver): intercept the reading of object attributes, such as proxy Foo and proxy['foo '].
  • set(target, propKey, value, receiver): intercept the setting of object properties, such as proxy Foo = V or proxy['foo'] = v, returns 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...in loop, returning an array. This method returns the property names of all its own properties of the target object, while object The returned result of keys () only includes 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. Define properties (proxy, propdescs) and return a Boolean value.
  • preventExtensions(target): intercept object Preventextensions (proxy), which returns a Boolean value.
  • getPrototypeOf(target): intercept object Getprototypeof (proxy), return 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): intercept Proxy instances as function calls, such as proxy(...args), Proxy call(object, ...args),proxy.apply(...).
  • construct(target, args): intercept the operation called by Proxy instance as constructor, such as new proxy(...args).

2.Proxy instance method

The following is a detailed introduction to the above interception methods.

get()

The get method is used to intercept the reading operation of a property. It can accept three parameters, namely, the target object, the property name and the proxy instance itself (strictly speaking, the object targeted by the operation behavior). The last parameter is optional.

There is an example of the usage of the get method above, and the following is another example of intercepting read operations.

var person = {
  name: "Zhang San"
};

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 // "Zhang San"
proxy.age // Throw an error

The above code indicates that if you access a property that does not exist in the target object, an error will be thrown. If you do not have this interceptor function, you will only return undefined when accessing non-existent properties.

The get method can inherit.

let proto = new Proxy({}, {
  get(target, propertyKey, receiver) {
    console.log('GET ' + propertyKey);
    return target[propertyKey];
  }
});

let obj = Object.create(proto);
obj.foo // "GET foo"

In the above code, the interception operation is defined on the Prototype object, so if you read the attributes inherited by the obj object, the interception will take effect.

The following example uses get interception to read the index of negative numbers from the 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);
    }
  };

  let target = [];
  target.push(...elements);
  return new Proxy(target, handler);
}

let arr = createArray('a', 'b', 'c');
arr[-1] // c

In the above code, if the position parameter of the array is - 1, the penultimate member of the array will be output.

Using Proxy, the operation of reading attributes (get) can be transformed into the execution of a function, so as to realize the chain operation of attributes.

var pipe = (function () {
  return 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]);
        return oproxy;
      }
    });

    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

After the Proxy is set in the above code, the effect of chaining the function name is achieved.

The following example uses get interception to implement a general function dom 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);
      }
      return el;
    }
  }
});

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);

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

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

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

const proxy = new Proxy({}, {
  get: function(target, key, receiver) {
    return receiver;
  }
});

const d = Object.create(proxy);
d.a === d // true

In the above code, the d object itself has no a attribute, so when reading d.a, you will go to the prototype proxy object of d to find it. At this time, the receiver points to d, which represents the object where the original read operation is located.

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

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

set()

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

Assuming that the Person object has an age attribute, which should be an integer no greater than 200, you can use Proxy to ensure that the attribute value of age 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');
      }
    }

    // For the age attribute and other attributes that meet the conditions, save them directly
    obj[prop] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = 'young' // report errors
person.age = 300 // report errors

In the above code, because the save value function set is set, any assignment of age attribute that does not meet the requirements will throw an error, which is an implementation method of data verification. Using the set method, you can also use data binding, that is, the DOM will be updated automatically whenever the object changes.

Sometimes, we will set internal properties on the object. The first character of the property name starts with an underscore, indicating that these properties should not be used externally. Combining the get and set methods can prevent these internal properties from being read and written by the outside.

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

In the above code, as long as the first character of the read-write attribute name is an underscore, all errors are thrown, so as to prohibit the reading and writing of internal attributes.

The following is an example of the fourth parameter of 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

In the above code, the fourth parameter receiver of the set method refers to the object where the original operation behavior is located. Generally, it is the proxy instance itself. Please see 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

In the above code, set myObj When the value of foo attribute is, myObj does not have foo attribute, so the engine will go to the prototype chain of myObj to find foo attribute. The prototype object Proxy of myObj is a Proxy instance. Setting its foo property will trigger the set method. At this time, 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 writable and configurable, the set method will not work.

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"

In the above code, obj The foo attribute cannot be written. The Proxy's set Proxy for this attribute will not take effect.

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

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

In the above code, in strict mode, if the set agent returns false or undefined, an error will be reported.

apply()

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

The apply method can accept three parameters: the target object, the context object (this) of the target object and the parameter array of the target object.

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

Here is 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"

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

Here is 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

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

In addition, directly call reflect The apply method will also be intercepted.

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

has()

The has method is used to intercept the HasProperty operation, that is, this method will take effect when judging whether an object has a property. A typical operation is the in operator.

The has method can accept two parameters: the target object and the attribute name 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 in target;
  }
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false

In the above code, if the first character of the attribute name of the original object is an underscore, proxy Has will return false and will not be found by the in operator.

If the original object is not configurable or extension is prohibited, the has interception will report an error.

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

var p = new Proxy(obj, {
  has: function(target, prop) {
    return false;
  }
});

'a' in p // TypeError is thrown

In the above code, the extension of obj object is prohibited. As a result, an error will be reported when 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 worth noting that the has method intercepts the HasProperty operation rather than the HasOwnProperty operation, that is, the has method does not judge whether a property is an object's own property or an inherited property.

Besides, although for The in loop also uses the in operator, but has intercepts the for The in cycle does not take effect.

let stu1 = {name: 'Zhang San', score: 59};
let stu2 = {name: 'Li Si', score: 99};

let handler = {
  has(target, prop) {
    if (prop === 'score' && target[prop] < 60) {
      console.log(`${target.name} fail,`);
      return false;
    }
    return prop in target;
  }
}

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

In the above code, has interception is only effective for the in operator and for The in loop does not take effect, resulting in non-conforming attributes not being used for Excluded by in cycle.

construct()

The construct method is used to intercept the new command. The following is the writing method of the intercepting object.

var handler = {
  construct (target, args, newTarget) {
    return new target(...args);
  }
};

The construct method can accept three parameters.

  • Target: target object
  • args: parameter object of constructor
  • newTarget: the constructor used by the new command when creating an instance object (p in the following example)
var 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

The return of the construct method must be an object, otherwise an error will be reported.

var p = new Proxy(function() {}, {
  construct: function(target, argumentsList) {
    return 1;
  }
});

new p() // report errors
// Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')

deleteProperty()

The deleteProperty method is used to intercept the delete operation. If this 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

In the above code, the deleteProperty method intercepts the delete operator. Deleting the property whose first character is underlined will report an error.

Note that the un configurable property of the target object itself cannot be deleted by the deleteProperty method, otherwise an error will be reported.

defineProperty()

The defineProperty method intercepted object defineProperty operation.

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

In the above code, the defineProperty method returns false, causing the addition of new properties to always be invalid.

Note that if the target object is non extensible, defineProperty cannot add properties that do not exist on the target object, otherwise an error will be reported. In addition, if a property of the target object is not writable or configurable, the defineProperty method must not change these two settings.

getOwnPropertyDescriptor()

The getOwnPropertyDescriptor method intercepts object getOwnPropertyDescriptor(), return 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 }

In the above code, handler The getownpropertydescriptor method returns undefined for property names whose first character is an underscore.

getPrototypeOf()

The getPrototypeOf method is mainly used to intercept and obtain object prototypes. Specifically, intercept the following operations.

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

Here is an example.

var proto = {};
var p = new Proxy({}, {
  getPrototypeOf(target) {
    return proto;
  }
});
Object.getPrototypeOf(p) === proto // true

In the above code, the getPrototypeOf method intercepts object getPrototypeOf(), return proto object.

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

isExtensible()

The isExtensible method intercepts object isExtensible operation.

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

Object.isExtensible(p)
// "called"
// true

The above code sets the isExtensible method when calling object called will be output when isExtensible.

Note that this method can only return Boolean values, otherwise the returned values will be automatically converted to Boolean values.

This method has a strong limitation. 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)

Here is 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')

ownKeys()

The ownKeys method is used to intercept the reading operation of the object's own properties. Specifically, intercept the following operations.

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

The following is to intercept 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' ]

The above code intercepts the object for the target object The keys () operation returns only the a attribute among the A, b and c attributes.

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"

Note that use object When using the keys method, three types of attributes will be automatically filtered by the ownKeys method and will not be returned.

  • A property that does not exist on the target object
  • The attribute name is Symbol value
  • Properties that are not enumerable
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']

In the above code, the ownKeys method explicitly returns the nonexistent attribute (d), Symbol value (Symbol.for('secret ') and non traversable attribute (key), and the results are automatically filtered out.

The 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); // No output
}

In the above code, ownkeys specifies to return only a and b attributes. Because obj does not have these two attributes, for The in loop will not have any output.

The array members returned by the ownKeys method can only be string or Symbol values. If there are other types of values, or the returned value is not an array at all, an error will be 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

In the above code, although the ownKeys method returns an array, 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, the 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'

In the above code, the a attribute of obj object is not configurable. At this time, the array returned by ownKeys method must contain a, otherwise an error will be reported.

In addition, if the target object is non extensible, the array returned by the ownKeys method must contain all the attributes of the original object and cannot contain redundant attributes, otherwise 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

In the above code, the obj object is not extensible. At this time, the array returned by the ownKeys method contains the redundant attribute b of the obj object, which leads to an error.

preventExtensions()

The preventextensions method intercepts object preventExtensions(). The method must return a Boolean value, otherwise it will be automatically converted to a Boolean value.

This method has a limitation. Only when the target object is not extensible (that is, Object.isExtensible(proxy) is false), proxy Only preventextensions can return true, 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

In the above code, proxy The preventextensions method returns true, but object Isextensible (proxy) will return true, so an error is reported.

In order to prevent this problem, it is usually in proxy In the preventextensions method, call object once preventExtensions.

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

Object.preventExtensions(proxy)
// "called"
// Proxy {}

setPrototypeOf()

setPrototypeOf method is mainly used to intercept object setPrototypeOf method.

Here is 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

In the above code, as long as the prototype object of target is modified, an error will be reported.

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

3.Proxy.revocable()

Proxy. The revocable method returns a revocable proxy instance.

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

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

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

revoke();
proxy.foo // TypeError: Revoked

Proxy. The revocable method returns an object whose proxy attribute is a proxy instance, and the revoke attribute is a function that can cancel the proxy instance. In the above code, an error will be thrown when the proxy instance is accessed after the revoke function is executed.

Proxy. A usage scenario of revocable is that the target object is not allowed to be accessed directly, but must be accessed through a proxy. Once the access is completed, the proxy right will be 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 of the target object, that is, it cannot guarantee the consistency with the behavior of the target object without any interception. The main reason is that in the case of Proxy proxy, the this keyword inside the target object will point to the Proxy.

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

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

In the above code, once the proxy proxy proxy target m. this in the latter refers to the proxy instead of the target.

The following is an example. Due to the change of this point, the Proxy cannot Proxy the target object.

const _name = new WeakMap();

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

In the above code, the name attribute of the target object jane is actually saved in the external WeakMap object_ Name is distinguished by this key. Because through proxy When name accesses, this points to the proxy, which makes it impossible to get the value, so it returns undefined.

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

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

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

In the above code, the getDate method can only be obtained on the Date object instance. If this is not a Date object instance, an error will be reported. At this time, this can solve this problem by binding the original object.

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

5. Instance: client of Web Service

The Proxy object can intercept any attribute of the target object, which makes it very suitable for clients who write Web services.

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

service.employees().then(json => {
  const employees = JSON.parse(json);
  // ยทยทยท
});

The above code creates a new Web service interface, which returns various data. Proxy can intercept any attribute of this object, so you don't need to write an adaptation method for each kind of data, just write a proxy interception.

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

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

Reprinted from Ruan Yifeng

Keywords: ECMAScript Vue Vue.js Proxy

Added by terryl on Sat, 29 Jan 2022 08:26:48 +0200