响应式原理
Vue中的响应式通过三种响应式解决方案,defineProperty、Proxy、value setter。
Vue2中使用 defineProperty API 实现响应式,存在的缺陷就是删除 obj.count 属性,set 函数不会执行,所以Vue2中需要使用 $delete 函数区删除数据。如下代码实现了简易的响应式功能,定义一个对象obj,使用 defineProperty 代理了 count 属性。这就实现了 obj 对象的 value 属性实现了拦截,读取 count 属性的时候执行 get 函数,修改 count 属性的时候 set 函数,并在 set 函数内部重新计算了 double 。
let getDouble = n=>n*2
let obj = {}
let count = 1
let double = getDouble(count)
Object.defineProperty(obj,'count',{
get(){
return count
},
set(val){
count = val
double = getDouble(val)
}
})
console.log(double) // 2
obj.count = 2
console.log(double) // 4
Vue3 的响应式机制是基于 Proxy 实现的,解决了 Vue2 响应式的缺陷。如以下代码, 通过 new Proxy 代理了 obj 对象,然后通过 get、set、deleteProperty 函数代理了对象的读取、修改和删除操作,从而实现了响应式的功能。
let getDouble = n=>n*2
let obj = {}
let count = 1
let double = getDouble(count)
let proxy = new Proxy(obj, {
get : function (target, prop) {
return target[prop]
},
set : function (target, prop, value) {
target[prop] = value;
if(prop === 'count') {
double = getDouble(value)
}
},
deleteProperty(target, prop) {
delete target[prop]
if(prop === 'count') {
double = NaN
}
}
})
console.log(obj.count, double) // undefined 2
proxy.count = 2
console.log(obj.count, double) // 2 4
delete proxy.count
console.log(obj.count, double) // undefined NaN
Vue3 Proxy 实现的功能和 Vue2 的 definePropery 类似,都能够在用户修改数据的时候出发 set 函数,更新 double ,Proxy 还完善了 definePropery 的缺陷,如监听属性的删除。Proxy 是针对对象来监听,而不是针对某个具体属性,所以不仅可以代理定义时不存在的属性,还可以代理更丰富的数据结构,比如 Map、Set ...,还能通过 deleteProperty 实现对删除操作的代理。
Vue3 除了 Proxy 还有另一个响应式实现的逻辑,利用对象的 get 和 set 函数来进行监听,这种响应式的实现方式,只能拦截某一个属性的修改,这也是 Vue3 中 ref 这个 API 实现的。如下代码中,拦截了 count 的value 属性,并且拦截了 set 操作,也能实现类似的功能。
let getDouble = n=>n*2
let _value = 1
double = getDouble(_value)
let count = {
get value() {
return _value
},
set value(val) {
_value = val
double = getDouble(_value)
}
}
console.log(count.value, double) // 1 2
count.value = 2
console.log(count.value, double) // 2 4
这三种实现原理对比如下:
实现原理 | defineProperty | Proxy | value setter |
实际场景 | Vue2 响应式 | Vue3 reactive | Vue3 ref |
优势 | 兼容性 | 基于Proxy实现真正的拦截 | 实现简单 |
劣势 | 数组和属性删除等拦截不了 | 不兼容IE11 | 只拦截了value属性 |
实际应用 | Vue2 | Vue3 复杂数据结构 | Vue3 简单数据结构 |