preface

Intercepting and customizing a JS object in JS is a very common scenario. Today, I’ll show you how to create a Proxy for an object using a Proxy object.

grammar

const p = new Proxy(target, handler)
Copy the code

parameter

  • target

The target object (which can be any type of object, including a native array, function, or even another Proxy) that you want to wrap with a Proxy.

  • handler

An object that typically has functions as attributes, and the functions in each attribute define the behavior of agent P when performing various operations

All catchers are optional. If a catcher is not defined, the default behavior of the source object is retained.

Handler’s catcher

handler.getPrototypeOf()

This method is called when the prototype of the proxy object is read.

  • grammar
Const p = new Proxy(obj, {// target target object to be proxied. GetPrototypeOf (target) {// ture, when the getPrototypeOf method is called, this refers to the handler object to which it belongs. Console. log(this === handler) // The return value of the getPrototypeOf method must be an object or NULL. return null } });Copy the code
  • There are five ways to trigger the getPrototypeOf() proxy method to run

    • Object.getprototypeof (), which returns the Prototype of the specified Object (the value of the internal [[Prototype]] property).

    • Reflect.getprototypeof (), which returns the Prototype of the specified object (that is, the value of the internal [[Prototype]] property).

    • __proto__ (This feature has been removed from the Web standard, although some browsers still support it, but may stop supporting it at some point in the future, please try not to use this feature.) Object. Prototype’s __proto__ property is an accessor property (a getter and a setter) that exposes the [[prototype]] (an Object or NULL) inside the Object accessed through it.

    • Object. The prototype. IsPrototypeOf (), is used to test whether an Object exists in the prototype chain of another Object.

      let arr = []
      let obj = {}
      // true
      console.log(Array.prototype.isPrototypeOf(arr))
      // false
      console.log(Array.prototype.isPrototypeOf(obj))
      Copy the code
    • Instanceof, which checks if the constructor’s prototype property appears on the prototype chain of an instance object.

      let arr = []
      let obj = {}
      // true
      console.log(arr instanceof Array)
      // false
      console.log(obj instanceof Array)
      Copy the code
  • example

let monster1 = { eyeCount: 4, test: 1 }; let monsterPrototype = { eyeCount: 20, age: 10 }; let handler = { getPrototypeOf(target) { return monsterPrototype; }}; let proxy1 = new Proxy(monster1, handler); monsterPrototype.age = 30 let proxy1Prototype = Object.getPrototypeOf(proxy1) // 30 console.log(proxy1Prototype.age, 1) // 20 console.log(proxy1Prototype.eyeCount, 2) // undefined console.log(proxy1Prototype.test, 3)Copy the code

handler.setPrototypeOf()

The handler.setPrototypeof () method is mainly used to intercept object.setPrototypeof () and reflect.setprototypeof ().

  • grammar
Var p = new Proxy(target, {// target, target) // prototype, null. Function (target, prototype) {// If [[prototype]] succeeds, setPrototypeOf returns true; otherwise, false. Return true}});Copy the code
  • example

If you don’t want to set a new prototype for your object, your handler’s setPrototypeOf method can either return false or throw an exception.

var handlerReturnsFalse = { setPrototypeOf(target, newProto) { return false; // or // throw new Error('custom error'); }}; var newProto = {}, target = {}; var p1 = new Proxy(target, handlerReturnsFalse); Object.setPrototypeOf(p1, newProto); // throws a TypeError Reflect.setPrototypeOf(p1, newProto); // returns falseCopy the code

handler.isExtensible()

The handler.isextensible () method is used to intercept object.isextensible () on an Object. The object.isextensible () method determines whether an Object isExtensible(whether new properties can be added to it).

  • grammar
Var p = new Proxy(target, {// target, target object isExtensible: The function(target) {// isExtensible method must return a Boolean value or a value that can be converted to Boolean. return true } });Copy the code
  • Pay attention to

Object. IsExtensible (proxy) must return the same value as Object. IsExtensible (target).

var p = new Proxy({}, { isExtensible: function(target) { return false; }}); // TypeError is thrown Object.isExtensible(p);Copy the code
var p = new Proxy({}, { isExtensible: function(target) { return false; }}); Object.preventExtensions(p); // Returns false Object.isextensible (p);Copy the code

handler.preventExtensions()

Handler. PreventExtensions () method is used to set on the Object. The preventExtensions intercept (). (The object.preventExtensions () method makes an Object unextensible, meaning new properties can never be added.)

  • grammar
Var p = new Proxy(target, {// target, target object to block. PreventExtensions: Function (target) {// Return a Boolean, if the target object is extensible false return true}});Copy the code
  • Pay attention to

Handler. PreventExtensions () interception Object. PreventExtensions () returns a Boolean value.

  • example
var p = new Proxy({}, { preventExtensions: function(target) { console.log('called'); Object.preventExtensions(target); return true; }}); // Return p console.log(object.preventExtensions (p));Copy the code

handler.getOwnPropertyDescriptor()

Handler. GetOwnPropertyDescriptor () for the Object. GetOwnPropertyDescriptor intercept () method (Object) getOwnPropertyDescriptor () Method returns a property descriptor corresponding to a property of its own on the specified object. (Own properties refer to properties that are directly assigned to the object and do not need to be looked up on the stereotype chain)

  • grammar
Var p = new Proxy (target, {/ / target: the target object, prop: returns the attribute names describe getOwnPropertyDescriptor: Function (target, prop) {// Return an object or undefined. return object } });Copy the code
  • example
var handler = { getOwnPropertyDescriptor (target, key) { if (key[0] === '_') { return undefined; } return Object.getOwnPropertyDescriptor(target, key); }}; var target = { _foo: 'foo', baz: 'baz' }; var proxy = new Proxy(target, handler); console.log(Object.getOwnPropertyDescriptor(proxy, 'wat')) // undefined console.log(Object.getOwnPropertyDescriptor(proxy, '_foo')) // undefined console.log(Object.getOwnPropertyDescriptor(proxy, 'baz'))Copy the code

handler.defineProperty()

Handler.defineproperty () is used to intercept object.defineProperty () operations on objects. The Object.defineProperty() method directly defines a new property on an Object, or modifies an existing property of an Object, and returns the Object.

  • grammar
Var p = new Proxy(target, {// target: indicates the target object. Property: indicates the attribute name to be retrieved. Descriptor: indicates the attribute descriptor to be defined or modified. DefineProperty: function(target, property, descriptor) {// Returns a Boolean indicating whether the operation to define the attribute succeeded or not. return true } });Copy the code
  • Intercept the following methods
    • Object.defineProperty()
    • Reflect.defineProperty()
    • proxy.property=’value’
  • example
var p = new Proxy({}, { defineProperty: function(target, prop, descriptor) { console.log('called: ' + prop); return true; }}); var desc = { configurable: true, enumerable: true, value: 10 }; Object.defineProperty(p, 'a', desc); // "called: a"Copy the code

handler.has()

Handler.has () is used for interception by the IN operator (in operator: The IN operator returns true if the specified property is in the specified object or its stereotype chain.)

  • grammar
Var p = new Proxy(target, {// target: target object, prop: has; Function (target, prop) {// Return a Boolean value true}});Copy the code
  • Intercept the following methods

    • Attribute query: Foo in Proxy
    • Inheritance attribute query: foo in object.create (proxy)
    • With check: with(proxy) {(foo); }
    • Reflect.has()
  • example

var obj = { a: 10 }; var p = new Proxy(obj, { has: function(target, prop) { return true; }}); 'a' in p;Copy the code
  • Pay attention to

If the following rules are violated, the proxy will raise TypeError:

  • If a property of the target object itself cannot be configured, the property cannot be hidden by the proxy.
  • If the target object is an unextensible object, the properties of that object cannot be hidden by the proxy
var obj = { a: 10 }; Object.preventExtensions(obj); var p = new Proxy(obj, { has: function(target, prop) { return false; }}); 'a' in p; // TypeError is thrownCopy the code

handler.get()

The handler.get() method is used to intercept an object’s read property operations.

  • grammar
Var p = new Proxy(target, {// target: target object, property: acquired attribute name, receiver: Proxy or inherit Proxy object get: Function (target, property, receiver) {// The get method can return any value. return any } });Copy the code
  • Intercept the following methods
  • Access properties: proxy[foo] and proxy.bar
  • Access properties on the prototype chain: Object.create(proxy)[foo]
  • Reflect.get()
  • example
var p = new Proxy({
  age: 10,
  name: 'lili'
}, {
  get: function(target, prop, receiver) {
   if(prop === 'age') { return 18 }
   return target[prop]
  }
});
// 18
console.log(p.age)
// lili
console.log(p.name)
Copy the code

handler.set()

The handler.set() method is the catcher for the set property value operation.

  • grammar
Const p = new Proxy(target, {// target: target object, property: attribute name to be set or Symbol, value: new attribute value, receiver: initial called object set: Function (target, property, value, receiver) {// Return a Boolean. return true } });Copy the code
  • Intercept the following methods

    • Specify attribute values: proxy[foo] = bar and proxy.foo = bar
    • Create (proxy)[foo] = bar
    • Reflect.set()
  • example

var p = new Proxy({}, {
  set: function(target, prop, value, receiver) {
    target[prop] = value
    return true;
  }
})

p.a = 102;               
// 102
console.log(p.a); 
Copy the code

handler.deleteProperty()

The handler.deleteProperty() method is used to intercept delete operations on object properties.

  • grammar
Var p = new Proxy(target, {// target: the target object, property: the name of the property to be deleted deleteProperty: Function (target, property) {// Returns a Boolean value indicating whether the property was successfully deleted. return true } });Copy the code
  • Intercept the following methods

    • Delete properties: delete proxy[foo] and delete proxy.foo
    • Reflect.deleteProperty()
  • example

var p = new Proxy({a: 123}, { deleteProperty: function(target, prop) { return false; }}); delete p.a; // 123 console.log(p.a)Copy the code

handler.ownKeys()

The handler.ownkeys () method is used to intercept reflect.ownkeys ()

  • grammar
Var p = new Proxy(target, {// target: target object ownKeys: function(target) {// return an enumerable object obj}});Copy the code
  • Intercept the following methods
    • Object.getOwnPropertyNames()
    • Object.getOwnPropertySymbols()
    • Object.keys()
    • Reflect.ownKeys()
  • example
var p = new Proxy({
 a: 1,
 b: 2
}, {
  ownKeys: function(target) {
    return Reflect.ownKeys(target)
  }
});
// ['a', 'b']
console.log(Object.keys(p))
Copy the code

handler.apply()

The handler.apply() method is used to intercept calls to functions.

  • grammar
Var p = new Proxy(target, {// target: the target object, thisArg: the context object when called, argumentsList: the argument array when called apply: Function (target, thisArg, argumentsList) {// Can return any}});Copy the code
  • Intercept the following methods

    • proxy(… args)
    • Function. The prototype. The apply (), and the Function. The prototype. The call ()
    • Reflect.apply()
  • example

var p = new Proxy(function() {}, { apply: function(target, thisArg, argumentsList) { return argumentsList[0] + argumentsList[1] + argumentsList[2]; }}); // 6 console.log(p(1, 2, 3));Copy the code

handler.construct()

The handler.construct() method is used to intercept the new operator. In order for the new operator to work on the generated Proxy object, the target object used to initialize the Proxy must itself have the [[Construct]] internal method (that is, the new target must be valid).

  • grammar
Var p = new Proxy(target, {// target: the target object, argumentsList: constructor's argument list, newTarget: the constructor originally called, in this case p. Construct: function(target, argumentsList, newTarget) {// Must return an object return obj}});Copy the code
  • Intercept the following methods
    • new proxy(… args)
    • Reflect.construct()
  • example
let p = new Proxy(function () {}, { construct: function(target, args) { return { value: args[0] * 10 }; }}); // { value: 10 } console.log(new p(1))Copy the code

scenario

Take a look at proxy usage examples to understand more

If the property name does not exist in the object, ‘the property is not defined’ is returned by default.

Example:

const handler = { get: function(obj, prop) { return prop in obj ? Obj [prop] : 'This property is not defined '; }}; const p = new Proxy({}, handler); p.a = 1; p.b = undefined; 1 undefined "the property is not defined" console.log(p.a, p.b, p.c); // 'c' in p // Checks if there is a 'c' attribute on p, returns true whether the attribute is in the prototype or in the instance // prints false console.log('c' in p);Copy the code

Validates a value passed to an object

For example, if the Person object has an age attribute, let’s add a condition that age can only be of type number and cannot exceed 100

Example:

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 > 100) { throw new RangeError('The age seems invalid'); } } obj[prop] = value; Return true; }}; let person = new Proxy({}, validator); person.age = 17; console.log(person.age); // 100 person.age = 'young'; Uncaught TypeError: The age is not an integer person. Age = 200; Uncaught TypeError: The age is not an integer person. // Throw an exception: Uncaught RangeError: The age seems invalidCopy the code

Manipulating DOM nodes

For example, set the aria-selected property of the last dom element to true and the previous ones to false

  • The HTML code
<! DOCTYPE HTML > < HTML > <head> <title> </head> <body> <div class="class1" id="1">aaa</div> <div class="class2"  id="2">bbb</div> <div class="class3" id="3">ccc</div> </body> </html> <style> .class1 { color: aqua; } .class2 { color: blue; } .class3 { color: pink; } </style>Copy the code
  • Js code
let view = new Proxy({ selected: null }, { set: function(obj, prop, newval) { let oldval = obj[prop]; if (prop === 'selected') { if (oldval) { oldval.setAttribute('aria-selected', 'false'); } if (newval) { newval.setAttribute('aria-selected', 'true'); } // The default behavior is to store the property value obj[prop] = newval; Return true; }}); let i1 = view.selected = document.getElementById('1'); let i2 = view.selected = document.getElementById('2'); let i3 = view.selected = document.getElementById('3');Copy the code
  • Final Execution result

Value modification and additional properties

Add the Products objects to the browsers browsers are arrays that store names, and browsers. LatestBrowser represents the latest browsers. How do we implement Products.browsers = XXX

let products = new Proxy({ browsers: ['Internet Explorer', 'Netscape'] }, { get: Function (obj, prop) {// Attach a property if (prop === 'latestBrowser') {return obJ. browsers[obJ. leng-1]; function(obj, prop) {// Attach a property if (prop === 'latestBrowser') {return obJ. browsers[obJ. leng-1]; } // Return obj[prop]; }, set: function(obj, prop, value) {// Browsers with additional properties if (prop === 'latestBrowser') {obJ.push (value); f. return; If (typeof value === 'string') {value = [value]; if (typeof value === 'string') {value = [value]; } // The default behavior is to save the property value obj[prop] = value; Return true; }}); console.log(products.browsers); // ['Internet Explorer', 'Netscape'] products.browsers = 'Firefox'; F you accidently pass in a string console.log(products.log); // ['Firefox'] <- returns an array of products.latestBrowser = 'Chrome'; console.log(products.browsers); // ['Firefox', 'Chrome'] console.log(products.latestBrowser); // 'Chrome'Copy the code

Why does Vue3.0 use Proxy instead of Object.defineProperty

First, VUE adopts data hijacking combined with publish-subscriber mode to achieve bidirectional binding

Disadvantages of object-defineProperty implementation of data hijacking

  • The first defect of Object.defineProperty is the inability to listen for array changes
  • We can only hijack the properties of an object, so we need to iterate over every property of each object. If the property values are also objects, then we need to iterate deeply. Obviously, hijacking a whole object is a better choice.

Proxy implements the advantages of data hijacking

  • Proxies can listen directly on objects rather than properties
  • The Proxy can listen for changes to the array directly
  • Proxy has up to 13 intercepting methods, not limited to apply, ownKeys, deleteProperty, has, and so on that Object. DefineProperty does not have.

The disadvantage of the Proxy

The disadvantage of Proxy is the compatibility problem, and it cannot be smoothed by polyfill

On this topic

For “why vue3.0 uses Proxy instead of Object.defineProperty”, I recommend juejin.cn/post/684490… This article is very detailed.

The last

Thank you for reading, if you have any questions please correct!