First, the Object. DefineProperty
Definition: The object.defineProperty () method directly defines a new property on an Object, or modifies an existing property of an Object, and returns the Object
Why is reactive
DefineProperty attributes, get and set
- get
Property that is called when the property is accessed. No arguments are passed, but this object is passed (because of inheritance, this is not necessarily the object that defines the property). The return value of this function is used as the value of the property
- set
Property, which is called when the property value is modified. This method takes an argument (that is, the new value being assigned) and passes in the this object at the time of assignment. The default value is undefined
The following is shown in code:
Define a reactive 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() } } }) }Copy the code
When defineReactive is called, data changes trigger the update method to achieve data responsiveness
const obj = {}
defineReactive(obj, 'foo'.' ')
setTimeout(() = >{
obj.foo = new Date().toLocaleTimeString()
},1000)
Copy the code
If an object has multiple keys, traversal is required
function observe(obj) {
if (typeofobj ! = ='object' || obj == null) {
return
}
Object.keys(obj).forEach(key= > {
defineReactive(obj, key, obj[key])
})
}
Copy the code
If there are nested objects, recursion in defineReactive is also required
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() } } }) }Copy the code
When you assign a key to an object, you also need to recurse in the set property
set(newVal) {
if(newVal ! == val) { observe(newVal)// The new value is the case of the object
notifyUpdate()
}
}
Copy the code
The above example can achieve basic responsiveness to an object, but there are still many problems
Deleting and adding attributes to an object cannot be hijacked
const obj = {
foo: "foo".bar: "bar"
}
observe(obj)
delete obj.foo // no ok
obj.jar = 'xxx' // no ok
Copy the code
It doesn’t work so well when we’re listening on an array
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
Copy the code
You can see that the API for the data can’t be hijacked to make the data responsive,
So in Vue2, the set and DELETE apis were added, and the array API methods were rewritten
Another problem is that if there are deep nested object relationships, the need for deep listening, resulting in a huge performance problem
summary
- The addition and deletion of object attributes cannot be detected
- An array of
API
Method cannot be listened on - Each attribute needs to be traversed and listened on, and if objects are nested, deep listening is required, causing performance problems
Second, the proxy
The listening of the Proxy is for an object, so all operations on that object go into the listening operation, which can completely Proxy all properties
We covered Proxy use in detail in the ES6 series, but we won’t cover it
The following is shown in code:
Define a reactive method
function reactive(obj) {
if (typeofobj ! = ='object'&& obj ! =null) {
return obj
}
// Proxy adds interception to an object
const observed = new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log(` access${key}:${res}`)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log(` set${key}:${value}`)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log(` delete${key}:${res}`)
return res
}
})
return observed
}
Copy the code
Test the manipulation of simple data and find that it can be hijacked
const state = reactive({
foo: 'foo'
})
/ / 1. Access
state.foo // ok
// 2. Set existing properties
state.foo = 'fooooooo' // ok
// 3. Set nonexistent properties
state.dong = 'dong' // ok
// 4. Delete attributes
delete state.dong // ok
Copy the code
When you test the nested objects, it’s not so OK
const state = reactive({
bar: { a: 1}})// Set nested object properties
state.bar.a = 10 // no ok
Copy the code
To resolve this, you need another layer of proxy on top of GET
function reactive(obj) {
if (typeofobj ! = ='object'&& obj ! =null) {
return obj
}
// Proxy adds interception to an object
const observed = new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log(` access${key}:${res}`)
return isObject(res) ? reactive(res) : res
},
return observed
}
Copy the code
Third, summary
Object.defineproperty can only be hijacked by traversing Object attributes
function observe(obj) {
if (typeofobj ! = ='object' || obj == null) {
return
}
Object.keys(obj).forEach(key= > {
defineReactive(obj, key, obj[key])
})
}
Copy the code
A Proxy can simply hijack an entire object and return a new object, so we can just manipulate the new object to be responsive
function reactive(obj) {
if (typeofobj ! = ='object'&& obj ! =null) {
return obj
}
// Proxy adds interception to an object
const observed = new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log(` access${key}:${res}`)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log(` set${key}:${value}`)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log(` delete${key}:${res}`)
return res
}
})
return observed
}
Copy the code
Proxies can listen for array changes directly (push, Shift, splice)
const obj = [1.2.3]
const proxtObj = reactive(obj)
obj.psuh(4) // ok
Copy the code
Proxy has up to 13 intercepting methods, not limited to Apply, ownKeys, deleteProperty, has, and so on, which Object. DefineProperty does not have
Because of the defects of defineProperty itself, Vue2 needs to implement other methods (such as rewriting array methods, adding extra set and delete methods) in the realization of reactive process.
// Array overwrite
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')
Copy the code
Proxy is not compatible with IE, there is no polyfill, defineProperty supports IE9
-
Reactive source code