当我们提到 Vue3 的优势时,往往会听到“响应式系统升级”这个说法,那么 Vue3 的响应式原理是如何实现的呢?本文将对 Vue3 的响应式系统和原理进行详细的介绍。
什么是响应式系统
在 Vue 中,数据驱动视图是一个非常重要的概念。当数据发生变化时,视图会自动更新,这是因为 Vue 实现了一个响应式系统,通过追踪依赖关系来自动更新视图。
简单来说,响应式系统就是 Vue 能够监听数据变化,并在变化时自动更新 DOM 的核心机制。在 Vue3 中,响应式系统的改进使得响应式数据可以更加高效地更新视图,同时也提升了开发体验和性能。
Vue2 的响应式原理
在 Vue2 中,响应式原理是通过 Object.defineProperty 来实现的。Vue 会在数据对象上定义 getter 和 setter,来实现数据变化时更新视图的功能。
举个例子,当一个数据对象被定义为响应式时,我们可以通过访问这个对象的属性来触发 get 操作。具体实现如下:
Object.defineProperty(obj, 'foo', {
get: function() {
console.log('get foo')
return obj._foo
},
set: function(newVal) {
console.log('set foo')
obj._foo = newVal
}
})
在上面的代码中,我们通过 Object.defineProperty 方法来监听对象上的属性 foo,当访问 foo 属性时,会触发 get 操作,并输出日志。同理,当给 foo 属性赋值时,会触发 set 操作,并输出日志。
在 Vue2 中,我们可以通过使用上面的方法来将数据对象中的所有属性转化成响应式的,以实现数据变化时更新视图的功能。
Vue3 的响应式原理
在 Vue3 中,响应式原理的实现方式和 Vue2 相比,有了相当大的变化。Vue3 直接使用了 ES6 Proxy 对象来拦截对对象的访问操作。Proxy 可以监听到对对象进行的访问、赋值等操作,并在这些操作发生时通知相关依赖以维护响应式系统的更新。
具体来说,当我们将一个数据对象定义为 reactive 对象时,Vue3 将在这个对象上建立一个 Proxy,这个 Proxy 会监视这个对象的所有属性,并收集相关依赖。当一个属性发生变化时,Proxy 会自动通知相关依赖进行更新,从而实现响应式的更新。
举个例子,当一个 reactive 对象中的属性被修改时,会通过 Proxy 的 set 拦截器来通知相关依赖进行更新。具体实现代码如下:
function reactive(target){
const handlers = {
get(target, key, receiver){
let result = Reflect.get(target, key, receiver)
return typeof result === 'object' ? reactive(result) : result
},
set(target, key, value, receiver){
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if(result && oldValue != value) effect()
return result
}
}
return new Proxy(target, handlers)
}
let product = reactive({price: 5, quantity: 2})
let total = 0
const effect = () => {
total = product.price * product.quantity
}
effect()
console.log('before set: ', total)
product.quantity = 3
console.log('after set: ', total)
console.log('quantity: ', product.quantity)
运行结果如下:
从上面的例子可以看到,当对象的某个属性值被修改了,set 拦截器监听到赋值操作,并对比新旧值是否一样,当值不一致时,会触发effec方法,这里的effect可以理解为更新视图的方法,它产生的作用就像vue中computed。
但上面的例子中可能有两个问题:
- 类似的effect方法可能有多个,就是针对于上面对象product的属性price影响到的页面节点可能有多个,我们平时开发的时候,可能会定义多个computed属性、watch方法。如果price属性的值被修改了,那么就需要去触发与其相关的影响effect,这时就需要有一个回调方法的集合去存储这些影响effect。
const depsMap = new Map()
function track(key){
let dep = depsMap.get(key)
if(!dep){
depsMap.set(key, (dep = new Set()))
}
dep.add(effect)
}
function trigger(key){
let dep = depsMap.get(key)
if(dep){
dep.forEach(effect => {
effect()
})
}
}
let product = { price: 5, quantity: 2 }
let total = 0
let effect = () => {
total = product.price * product.quantity
}
track('quantity')
effect() // total = 10
product.quantity = 3
trigger('quantity')
console.log(total) // total = 15
我们定义一个map集合,对象属性作为key,value是set集合,存储对象属性所有的回调方法,当对象属性值被修改时,会调用trigger方法获取属性对应的回调方法集合,循环执行。
- 在平时的开发过程中,我们肯定有多个对象加入到响应式中,这时就需要一个集合去存储多个对象的响应式回调方法集合。
这里我们使用了一个WeakMap集合,对象作为key,depsMap作为value,存储多个对象的响应式回调方法。
WeakMap是一个键值对的集合,键必须为一个对象,值是任意的,而且键是弱引用,如果没有存在其他引用的情况下,是可以正常地进行垃圾回收,这对内存的使用非常友好,自然而然地性能也提升上去,因此WeakMap正好适用于我们上面的使用场景。
最终的代码如下:
const targetMap = new WeakMap()
function track(target, key){
let depsMap = targetMap.get(target)
if(!depsMap){
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if(!dep){
depsMap.set(key, (dep = new Set()))
}
dep.add(effect)
}
function trigger(target, key){
const depsMap = targetMap.get(target)
if(!depsMap) return
let dep = depsMap.get(key)
if(dep){
dep.forEach(effect => {
effect()
})
}
}
function reactive(target){
const handlers = {
get(target, key, receiver){
let result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver){
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if(result && oldValue != value){
trigger(target, key)
}
return result
}
}
return new Proxy(target, handlers)
}
let product = reactive({price: 5, quantity: 2})
let total = 0
const effect = () => {
total = product.price * product.quantity
}
effect()
console.log('before set: ', total)
product.quantity = 3
console.log('after set: ', total)
console.log('quantity: ', product.quantity)
总结
在 Vue3 中,响应式系统的实现方式使用了 ES6 Proxy 对象来拦截数据对象的访问操作,避免了 Vue2 中 Object.defineProperty 无法监听数组变化等问题,使得响应式数据更新更加高效、可预测和易维护。
同时,Vue3 的依赖收集机制也进行了一定的优化,使用基于函数的依赖收集提高了系统的性能,同时也使得开发人员可以更方便地跟踪和定位问题。
最后,Vue3 在响应式原理的改进和优化为我们提供了更加高效、可靠和强大的开发体验。