This is similar to the data binding function in popular front-end frameworks (e.g. React, Vue, etc.). DOM rendering is automatically updated when data is updated. How to implement data binding?
This paper gives two ideas:
- ES5 Object. DefineProperty
- The Proxy ES6
ES5 Object. DefineProperty
The object.defineProperty () method directly defines a new property on an Object, or modifies an existing property of an Object, and returns the Object
– the MDN
Object.defineProperty(obj, prop, descriptor)
Copy the code
Among them:
obj
: The object for which attributes are to be definedprop
: The name or of the property to be defined or modifiedSymbol
descriptor
: Property descriptor to define or modify
var user = {
name: 'sisterAn'
}
Object.defineProperty(user, 'name', {
enumerable: true.configurable:true.set: function(newVal) {
this._name = newVal
console.log('set: ' + this._name)
},
get: function() {
console.log('get: ' + this._name)
return this._name
}
})
user.name = 'an' // set: an
console.log(user.name) // get: an
Copy the code
Listen for each child of a variable if it is complete:
// Monitor object
function observe(obj) {
// Iterate over the object, redefining each property value of the object using get/set
Object.keys(obj).map(key= > {
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, k, v) {
// Recursive subattributes
if (typeof(v) === 'object') observe(v)
// redefine get/set
Object.defineProperty(obj, k, {
enumerable: true.configurable: true.get: function reactiveGetter() {
console.log('get: ' + v)
return v
},
// When a value is reset, the collector's notification mechanism is triggered
set: function reactiveSetter(newV) {
console.log('set: ' + newV)
v = newV
},
})
}
let data = {a: 1}
// Monitor object
observe(data)
data.a // get: 1
data.a = 2 // set: 2
Copy the code
Traversal through map, listening for subchild attributes through deep recursion
Note that Object.defineProperty has the following defects:
- Internet Explorer 8 and earlier are not supported
- Unable to detect additions or deletions of object properties
- If you modify the array
length
(Object.defineProperty
Cannot listen on array length), as well as array’spush
Such a mutation method is unable to triggersetter
的
How does vue2. X solve this problem?
How to monitor array changes in vue2. X
Using the method of function hijacking, rewrite the array method, Vue will be in the data array prototype chain rewrite, pointing to their own definition of the array prototype method. This allows dependency updates to be notified when the array API is called. If the array contains a reference type, a recursive traversal of the array is monitored. This allows you to monitor array changes.
For arrays, Vue internally rewrites the following functions to distribute updates
// Get the array prototype
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// Rewrite the following functions
const methodsToPatch = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
methodsToPatch.forEach(function (method) {
// Cache native functions
const original = arrayProto[method]
// Rewrite the function
def(arrayMethods, method, function mutator (. args) {
// Call the native function first to get the result
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
// Listen for new data when the following functions are called
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// Manually send updates
ob.dep.notify()
return result
})
})
Copy the code
How does vue2. X solve the problem that adding attributes to objects does not trigger component rerendering
Restricted by modern JavaScript (Object.Observe has been deprecated), Vue cannot detect additions or deletions of Object attributes.
Since Vue performs getter/setter conversions on the property when it initializes the instance, the property must exist on the data object for Vue to convert it to reactive.
Vue does not allow dynamic root-level reactive attributes to be added to already created instances. However, you can add reactive properties to nested objects using the vue.set (Object, propertyName, value) method.
Vm.$set() implementation principle
export function set(target: Array<any> | Object, key: any, val: any) :any {
// Target is an array
if (Array.isArray(target) && isValidArrayIndex(key)) {
// Change the length of the array so that splice() is not executed incorrectly due to index > array length
target.length = Math.max(target.length, key);
// Use the array splice method to trigger the response
target.splice(key, 1, val);
return val;
}
// Target is an Object. Key must be on target or target.prototype and must not be directly assigned to object. prototype
if (key intarget && ! (keyin Object.prototype)) {
target[key] = val;
return val;
}
If none of the above is true, start creating a new property for target
// Get an Observer instance
const ob = (target: any).__ob__;
// Target itself is not reactive data, directly assigned
if(! ob) { target[key] = val;return val;
}
// Perform reactive processing
defineReactive(ob.value, key, val);
ob.dep.notify();
return val;
}
Copy the code
- If the target is an array, use the variation method implemented by VUE
splice
Implementing responsiveness - If the target is an object, the attribute is judged to exist, that is, it is reactive and directly assigned
- if
target
It’s not reactive, it’s directly assigned - Is called if the property is not reactive
defineReactive
Method for reactive processing
The Proxy ES6
It is well known that Vue3.0 uses Proxy instead of defineProperty for data binding because Proxy can directly listen for changes in objects and arrays and has up to 13 interception methods. And as a new standard, browser vendors will continue to focus on performance optimization.
Proxy
Proxy objects are used to create a Proxy for an object to intercept and customize basic operations (such as property lookup, assignment, enumeration, function calls, and so on)
– MDN
const p = new Proxy(target, handler)
Copy the code
Among them:
target
: to useProxy
Wrapped target object (can be any type of object, including a native array, a function, or even another proxy)handler
: an object that usually has functions as attributes, and the functions in each attribute define the agents that perform the various operationsp
The behavior of the
var handler = {
get: function(target, name){
return name in target ? target[name] : 'no prop! '
},
set: function(target, prop, value, receiver) {
target[prop] = value;
console.log('property set: ' + prop + '=' + value);
return true; }};var user = new Proxy({}, handler)
user.name = 'an' // property set: name = an
console.log(user.name) // an
console.log(user.age) // no prop!
Copy the code
As mentioned above, Proxy provides a total of 13 interception behaviors, which are:
getPrototypeOf
/setPrototypeOf
isExtensible
/preventExtensions
ownKeys
/getOwnPropertyDescriptor
defineProperty
/deleteProperty
get
/set
/has
apply
/construct
If you’re interested, check out MDN and try them out. I won’t repeat them here
Consider two other questions:
- A Proxy only proxies the first layer of an object, so how does that work?
- How do you prevent multiple get/set triggers when monitoring arrays?
Vue3 Proxy
For the first question, we can determine if the current reflect. get return value is Object, and if so, we can proxy it by Reactive.
For the second question, we can determine if it is hasOwProperty
Here we write a case to customize the behaviors of acquiring, adding and deleting through proxy
const toProxy = new WeakMap(a);// Store the proxied object
const toRaw = new WeakMap(a);// Store the proxied object
function reactive(target) {
// Create a responsive object
return createReactiveObject(target);
}
function isObject(target) {
return typeof target === "object"&& target ! = =null;
}
function hasOwn(target,key){
return target.hasOwnProperty(key);
}
function createReactiveObject(target) {
if(! isObject(target)) {return target;
}
let observed = toProxy.get(target);
if(observed){ // Determine whether the proxy is used
return observed;
}
if(toRaw.has(target)){ // Determine whether to duplicate the proxy
return target;
}
const handlers = {
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver);
track(target,'get',key); // Rely on collection ==
returnisObject(res) ? reactive(res):res; },set(target, key, value, receiver) {
let oldValue = target[key];
let hadKey = hasOwn(target,key);
let result = Reflect.set(target, key, value, receiver);
if(! hadKey){ trigger(target,'add',key); // Trigger add
}else if(oldValue ! == value){ trigger(target,'set',key); // Trigger the change
}
return result;
},
deleteProperty(target, key) {
console.log("Delete");
const result = Reflect.deleteProperty(target, key);
returnresult; }};// Start the proxy
observed = new Proxy(target, handlers);
toProxy.set(target,observed);
toRaw.set(observed,target); // create a mapping table
return observed;
}
Copy the code
conclusion
Advantages of Proxy over defineProperty:
- Based on the
Proxy
和Reflect
, can listen on native arrays, can listen on object attributes added and removed - No deep traversal listening required: judge the current
Reflect.get
Is the return value ofObject
If yes, pass againreactive
Method as a proxy, so as to achieve depth observation - Only in the
getter
The next layer of the object is hijacked (optimized for performance)
Therefore, it is recommended to use Proxy to monitor variable changes
reference
- MDN
- Vue – Next (VUE 3.0) is perfect
Three minutes a day