preface
In Vue 3.0, Proxy replaces Object.defineProperty. What is Proxy, what can Proxy do, what is the difference between Object.defineProperty and Proxy in Vue, and why it should be replaced
What is Proxy?
Proxy objects are used to define custom behaviors of basic operations (such as attribute lookup, assignment, enumeration, function call, etc.).
Generally speaking, Proxy is an interceptor of object operation, which intercepts the operation of the target object and performs some custom behaviors.
How does Proxy work?
let p = new Proxy(target, handler);
Target the target object wrapped in Proxy (can be any type of object, including a native array, a function, or even another Proxy).
handler an object whose properties are functions that define the behavior of an agent when an operation is performed.
[for example]
var proxy = new Proxy({}, { get: function(target, property) { return 35; } }); let obj = Object.create(proxy); obj.time // 35
No action forwarding agent
The proxy object p will forward all operations applied to it to the target object target. You can directly operate on the proxy object, and the operations will be forwarded to the target object.
[for example]
let target = {}; let p = new Proxy(target, {}); p.a = 37; // Forward operation to destination console.log(target.a); // 37. The operation has been forwarded correctly
Operations that can be intercepted
There are 13 kinds of proxy operations. The code (property name / method name) of each operation and the way to trigger this operation are listed below. Note that if an operation is not defined, it will be forwarded to the target object.
handler.getPrototypeOf()
This operation is triggered when reading the prototype of the proxy object, such as when executing Object.getPrototypeOf(proxy).
handler.setPrototypeOf()
This operation is triggered when setting the prototype of the proxy object, such as when executing Object.setPrototypeOf(proxy, null).
handler.isExtensible()
This operation is triggered when determining whether a proxy object is extensible, such as when executing Object.isExtensible(proxy).
handler.preventExtensions()
This operation is triggered when a proxy object is made non extensible, such as when executing Object.preventExtensions(proxy).
handler.getOwnPropertyDescriptor()
This operation is triggered when obtaining the property description of a property of the proxy object, such as when executing Object.getOwnPropertyDescriptor(proxy, "foo").
handler.defineProperty()
This operation is triggered when defining the property description of a property of the proxy object, such as when executing Object.defineProperty(proxy, "foo", {}).
handler.has()
This operation is triggered when determining whether the proxy object has a property, such as when executing "foo" in proxy.
handler.get()
This operation is triggered when a property of the proxy object is read, such as when proxy.foo is executed
handler.set()
This operation is triggered when assigning a value to a property of the proxy object, such as when proxy.foo = 1 is executed.
handler.deleteProperty()
This operation is triggered when a property of the proxy object is deleted, such as when delete proxy.foo is executed.
handler.ownKeys()
This operation is triggered when all the property keys of the proxy object are obtained, such as when executing Object.getOwnPropertyNames(proxy).
handler.apply()
Triggered when the target object is a function and is called.
handler.construct()
This operation is triggered when constructing an instance of a proxy object whose target object is a constructor, such as when executing new proxy().
The direction of this
Special attention should be paid to this pointing in proxy objects and interception operations
Once the object is proxied, his this points to the proxy object
const target = { m: function () { console.log(this === proxy); } }; const handler = {}; const proxy = new Proxy(target, handler); target.m() // false proxy.m() // true
In the interception operation defined by the handler, this points to the handler and the receiver points to the proxy
const target = { m: 100 }; const handler = { get(target, property,receiver){ console.log(this === handler) console.log(receiver === proxy) return target[property] } }; const proxy = new Proxy(target, handler); console.log(proxy.m)
What can Proxy do?
What can Proxy do with various commonly used interceptions? There are four commonly used operations
get()
The get method is used to intercept the read operation of a property. It can accept three parameters, namely, the target object, the property name and the proxy instance itself. All property calls will enter the same get, even if there are no properties.
- You can create properties that you don't have
- Data can be verified and converted when fetching data
- You can customize some syntax operations
- If get returns a function, it can convert a property into a method
[for example]
var base = { a:100, small:"hello world!!" } var proxy = new Proxy(base,{ get(target, property,receiver){ //Property to function if("fn" === property ){ return function(value){ console.log(value) } } if("ghost" === property ){ return "ghost" } if("fn" === property ){ return function(value){ console.log(value) } } //Custom syntax sugar if(property.includes("_")){ const direct = property.split("_")[1] const propertyBase = property.split("_")[0] switch (direct){ case "Big": return receiver[propertyBase].toLocaleUpperCase() default: break; } } if(!(property in target)){ throw new ReferenceError("Property \"" + property + "\" does not exist.");//Validate attribute values } return target[property] } }) console.log(proxy.a)//Output 100 normal access proxy.fn("fn")//Convert output fn attribute to method console.log(proxy.small_Big)//Output HELLO WORLD!! Custom syntax sugar console.log(proxy.ghost)//Output ghost create attribute console.log(proxy.ghost_Big)//Use of output ghost receiver console.log(proxy.b)//Error thrown during data validation
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.
- It can be used to verify whether the attribute meets the requirements
- Can be used to change the data format
- Can be used to listen for data change events
- Mask some assignment operations, such as "" Private variable at the beginning
[for example]
var base = { a:100, small:"hello world!!" } const A_Change_Event="aChangeEvent"; window.addEventListener(A_Change_Event,(e)=>{ console.log(e.data.value) }) var proxy = new Proxy(base,{ set(obj, property, value,receiver){ if("a" === property ){ if (!Number.isInteger(value)) { throw new TypeError('The'+property+'is not an integer'); } if (value > 100){ throw new RangeError('The '+property+' seems invalid'); } obj[property] = value; //event const dataChangeEvent = new Event(A_Change_Event);//Create an event dataChangeEvent.data = {value} window.dispatchEvent(dataChangeEvent) } } }) proxy.a = 80; console.log(proxy.a) proxy.a = 120;
apply()
The function object can also be the target object of the proxy
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.
[for 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"
construct ()
The new operation is intercepted by construct
Accept three parameters
- Target: target object
- args: parameter object of constructor
- newTarget: the constructor used by the new command when creating an instance object
Proxy and Object.defineProperty in Vue
Object.defineProperty implements observe
Recursively traverse all properties, and use Object.defineProperty to define listening one by one
var data = {name: 'kindeng'}; observe(data); data.name = 'dmq'; // Hahaha, the listening value has changed, King -- > DMQ function observe(data) { if (!data || typeof data !== 'object') { return; } // Take out all attribute traversal Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }); }; function defineReactive(data, key, val) { observe(val); // Listening sub attribute Object.defineProperty(data, key, { enumerable: true, // enumerable configurable: false, // Can no longer define get: function() { return val; }, set: function(newVal) { console.log('Hahaha, the monitoring value has changed ', val, ' --> ', newVal); val = newVal; } }); }
Proxy implements observe
There is no need to traverse the binding one by one in advance. Judge by key every time
observe(data) { const that = this; let handler = { get(target, property) { return target[property]; }, set(target, key, value) { let res = Reflect.set(target, key, value); that.subscribe[key].map(item => { item.update(); }); return res; } } this.$data = new Proxy(data, handler); }
Why use Proxy instead of Object.defineProperty?
vue3.0 uses Proxy instead of traversing the object, and uses the Object.defineProperty method to add set and get accessors to the property. In other words, you don't need to traverse, but directly monitor the data object.