vue3的一大更新就是数据绑定,新的响应式机制采用了 ES6 的Proxy,抛弃了 Object.defineProperty()
vue2
核心方法:Object.defineProperty(),通过defineProperty两个属性,get和set来实现。
get:一个给属性提供getter的方法,如果没有getter则为undefined。该方法返回值被用作属性值。默认为undefined。
set:一个给属性提供setter的方法,如果没有setter则为undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认值为undefined。
先看个例子
let obj = {}, value = 1;
Object.defineProperty(obj,'a',{
get() {
console.log('这里监听到了数据获取')
// get方法必须ruturn值,值为全局变量。
return value
},
set(newValue) {
if(newValue !== value) {
value = newValue
console.log('这里监听到了数据更改')
}
}
})
console.log(obj.a) // 这里监听到了obj数据获取
// 值改为 1
obj.a = 2 // 这里监听到了数据更改
在vue2中,对data进行了劫持,每个属性都通过Object.defineProperty()的get/set来获取和更改,一旦发生改变,就会触发set,然后去更新view。
let data = {
name: 'nike',
info: {
age: 21
}
}
Object.keys(data).forEach(key=>{
myDefineProperty(data, key, data[key])
})
function myDefineProperty(target, key, value) {
Object.defineProperty(target,key,{
get() {
console.log('这里监听到了数据获取')
return value
},
set(newValue) {
if(newValue !== value) {
value = newValue
console.log('这里监听到了数据更改')
}
}
})
}
data.name = 'tom' // 这里监听到了数据更改
data.info.age = 22 // 这里监听到了数据获取
data.info = {age:22} // 这里监听到了数据更改
这里更改了data.info.age却没有监听到数据更改,从而无法实现响应式,所以在Vue2中,增加了set、delete API,并且对数组api方法进行一个重写。
同时,对于存在深层嵌套的对象,需要通过watch进行监听,这里的deep会对该属性递归遍历并劫持,类似于深拷贝。
watch: {
info: {
handler(){},
deep: true
}
}
Object.defineProperty()还无法对数组监听,因为数组没有key,所以vue对数组进行了重写。
总结起来Object.defineProperty()的问题就是:
- 检测不到对象属性添加或删除。
- 数组方法无法监听到。
- 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听。
vue3
核心:
核心方法:ES6的Proxy,点击这里了解 什么是proxy?
Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的
let data = {
msg: {
a: 10
},
arr: [1, 2]
}
let handler = {
get(target, key) {
// 去获取的时候才监听对象里面的对象,而不是直接递归循环监听
console.log('获取key: ' + key)
if (typeof target[key] === 'object' && target[key] !== null) {
// 需要对引用嵌套对象再上一层劫持
return new Proxy(target[key], handler)
}
return Reflect.get(target, key)
},
set(target, key, value) {
let oldValue = target[key]
console.log('更新key: ' + key)
if (oldValue !== value) {
// 通知view更新
}
return Reflect.set(target, key, value)
}
}
let proxy = new Proxy(data, handler)
proxy.arr.push(3)
console.log(proxy.arr)
// {
// 0: 1
// 1: 2
// 2: 3
// length:: 3
// }
proxy可以直接使用数组方法。
我们再来封装一下,实现一个简单的reactive:
function reactive(obj) {
if (typeof obj !== 'object' && obj != null) {
return obj
}
// Proxy相当于在对象外层加拦截
const observed = new Proxy(obj, {
get(target, key) {
// 去获取的时候才监听对象里面的对象,而不是直接递归循环监听
console.log('获取key: ' + key)
if (typeof target[key] === 'object' && target[key] !== null) {
// 需要对引用嵌套对象再上一层劫持
return new Proxy(target[key], handler)
}
return Reflect.get(target, key)
},
set(target, key, value) {
let oldValue = target[key]
console.log('更新key: ' + key)
if (oldValue !== value) {
// 通知view更新
}
return Reflect.set(target, key, value)
}
})
return observed
}
const obj = [1,2]
const proxy = reactive(obj)
proxy.psuh(3) // { 0: 1, 1: 2, 2: 3, length: 3 }