1, Object defineProperty
Definition: object The defineproperty () method will directly define a new property on an object, or modify an existing property of an object and return the object
Why can we achieve responsiveness
Define set property and define get property
-
get
Property. This function will be called when the property is accessed. No parameters are passed in during execution, but this object will be passed in (due to inheritance, this here is not necessarily the object that defines this attribute). The return value of this function is used as the value of the property
-
set
Property. This function will be called when the property value is modified. This method accepts a parameter (that is, the new value assigned) and passes in the this object at the time of assignment. The default is undefined
The following code shows:
Define a responsive function defineReactive
function update() { app.innerText = obj.foo } function defineReactive(obj, key, val) { Object.defineProperty(obj, key, { get() { console.log(`get ${key}:${val}`); return val }, set(newVal) { if (newVal !== val) { val = newVal update() } } }) }
Call defineReactive, and the update method is triggered when the data changes, so as to realize the data response
const obj = {} defineReactive(obj, 'foo', '') setTimeout(()=>{ obj.foo = new Date().toLocaleTimeString() },1000)
When there are multiple key s in the object, traversal is required
function observe(obj) { if (typeof obj !== 'object' || obj == null) { return } Object.keys(obj).forEach(key => { defineReactive(obj, key, obj[key]) }) }
If there are nested objects, you also need to recurse in defineReactive
function defineReactive(obj, key, val) { observe(val) Object.defineProperty(obj, key, { get() { console.log(`get ${key}:${val}`); return val }, set(newVal) { if (newVal !== val) { val = newVal update() } } }) }
When assigning a key as an object, you also need to recurse in the set attribute
set(newVal) { if (newVal !== val) { observe(newVal) //The new value is the case of the object notifyUpdate() } }
The above example can realize the basic response to an object, but there are still many problems
You can't hijack an object by deleting and adding attributes
const obj = { foo: "foo", bar: "bar" } observe(obj) delete obj.foo // no ok obj.jar = 'xxx' // no ok
When we listen to an array, it's not so easy
const arrData = [1,2,3,4,5]; arrData.forEach((val,index)=>{ defineProperty(arrData,index,val) }) arrData.push() // no ok arrData.pop() // no ok arrDate[0] = 99 // ok
It can be seen that the api of the data cannot be hijacked, so the data response cannot be realized,
Therefore, in Vue2, set and delete APIs are added, and the array API method is rewritten
Another problem is that if there is a deep nested object relationship, it needs deep listening, resulting in a great performance problem
Summary
-
The addition and deletion of object properties were not detected
-
Array API method cannot listen
-
Each attribute needs to be traversed and monitored. If nested objects need to be deeply monitored, resulting in performance problems
2, proxy
Proxy listening is for an object, so all operations on this object will enter the listening operation, which can completely proxy all properties
In the ES6 series, we have explained the use of Proxy in detail, so we won't talk about it any more
The following is shown by code:
Define a reactive method
function reactive(obj) { if (typeof obj !== 'object' && obj != null) { return obj } //Proxy is equivalent to adding interception on the outer layer of the object const observed = new Proxy(obj, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver) console.log(`obtain ${key}:${res}`) return res }, set(target, key, value, receiver) { const res = Reflect.set(target, key, value, receiver) console.log(`set up ${key}:${value}`) return res }, deleteProperty(target, key) { const res = Reflect.deleteProperty(target, key) console.log(`delete ${key}:${res}`) return res } }) return observed }
Test the operation of simple data and find that it can be hijacked
const state = reactive({ foo: 'foo' }) // 1. obtain state.foo // ok // 2. Set existing properties state.foo = 'fooooooo' // ok // 3. Set non-existent property state.dong = 'dong' // ok // 4. Delete attribute delete state.dong // ok
Then test the nested objects. At this time, it is found that it is not so OK
const state = reactive({ bar: { a: 1 } }) //Set nested object properties state.bar.a = 10 // no ok
If you want to solve this problem, you need another layer of proxy on top of get
function reactive(obj) { if (typeof obj !== 'object' && obj != null) { return obj } //Proxy is equivalent to adding interception on the outer layer of the object const observed = new Proxy(obj, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver) console.log(`obtain ${key}:${res}`) return isObject(res) ? reactive(res) : res }, return observed }
3, Summary
Object.defineProperty can only traverse object properties for hijacking
function observe(obj) { if (typeof obj !== 'object' || obj == null) { return } Object.keys(obj).forEach(key => { defineReactive(obj, key, obj[key]) }) }
Proxy can directly hijack the whole object and return a new object. We can only operate the new object to achieve the purpose of responsiveness
function reactive(obj) { if (typeof obj !== 'object' && obj != null) { return obj } //Proxy is equivalent to adding interception on the outer layer of the object const observed = new Proxy(obj, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver) console.log(`obtain ${key}:${res}`) return res }, set(target, key, value, receiver) { const res = Reflect.set(target, key, value, receiver) console.log(`set up ${key}:${value}`) return res }, deleteProperty(target, key) { const res = Reflect.deleteProperty(target, key) console.log(`delete ${key}:${res}`) return res } }) return observed }
Proxy can directly monitor the changes of the array (push, shift, splice)
const obj = [1,2,3] const proxtObj = reactive(obj) obj.psuh(4) // ok
Proxy has up to 13 interception methods, not limited to apply, ownKeys, deleteProperty, has, etc. This is object Defineproperty does not have
Because of the defects of defineProperty itself, Vue2 needs to implement other auxiliary methods (such as rewriting array methods, adding additional set and delete methods) in the process of implementing responsiveness
//Array rewriting const originalProto = Array.prototype const arrayProto = Object.create(originalProto) ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => { arrayProto[method] = function () { originalProto[method].apply(this.arguments) dep.notice() } }); // set,delete Vue.set(obj,'bar','newbar') Vue.delete(obj),'bar')
Proxy , is not compatible with ie, nor does , polyfill, defineProperty , support IE9