Vue3响应式系统从Object.defineProperty到Proxy的演进
Vue2的响应式系统基于Object.defineProperty实现,通过getter/setter劫持对象属性的读写操作。这种方案存在已知局限:无法检测属性的新增和删除,无法监听数组索引变化和长度修改,需要$set和$delete等辅助API。深层对象需要递归遍历,初始化时性能开销大。
Vue3采用ES6 Proxy重写了整个响应式系统。Proxy可以代理整个对象的所有操作,包括属性新增、删除、数组索引修改等,不需要递归遍历。配合Reflect API,响应式系统的代码更简洁、性能更优、覆盖场景更完整。
reactive函数的实现原理与Proxy拦截
reactive是Vue3响应式系统的核心函数,将普通对象转换为响应式代理对象。简化后的实现逻辑:
// Vue3 reactive 简化实现
const reactiveMap = new WeakMap()
function reactive(target) {
// 原始值不做响应式处理
if (typeof target !== 'object' || target === null) {
return target
}
// 如果已经是响应式对象,直接返回
if (target.__v_isReactive) {
return target
}
// 缓存检查,避免重复代理
if (reactiveMap.has(target)) {
return reactiveMap.get(target)
}
const proxy = new Proxy(target, {
get(obj, key, receiver) {
if (key === '__v_isReactive') return true
const result = Reflect.get(obj, key, receiver)
// 依赖收集:记录谁在读取这个属性
track(obj, key)
// 深层响应式:递归代理嵌套对象(懒代理)
if (typeof result === 'object' && result !== null) {
return reactive(result)
}
return result
},
set(obj, key, value, receiver) {
const oldValue = obj[key]
const result = Reflect.set(obj, key, value, receiver)
// 只在新旧值不同时触发更新
if (oldValue !== value) {
// 派发更新:通知依赖该属性的effect重新执行
trigger(obj, key)
}
return result
},
deleteProperty(obj, key) {
const result = Reflect.deleteProperty(obj, key)
trigger(obj, key)
return result
}
})
reactiveMap.set(target, proxy)
return proxy
}
关键设计点:使用WeakMap缓存代理对象,避免对同一原始对象多次创建Proxy;嵌套对象采用懒代理策略,只在首次访问时递归代理,减少初始化开销;track和trigger分别负责依赖收集和派发更新。
ref函数与基本类型响应式的实现
reactive只能处理对象类型,基本类型(string、number、boolean)无法被Proxy代理。Vue3通过ref函数解决这一限制,将基本类型包装为对象:
// Vue3 ref 简化实现
class RefImpl {
constructor(value) {
this._value = isObject(value) ? reactive(value) : value
this.__v_isRef = true
}
get value() {
// 依赖收集
track(this, 'value')
return this._value
}
set value(newVal) {
if (newVal !== this._value) {
this._value = isObject(newVal) ? reactive(newVal) : newVal
// 派发更新
trigger(this, 'value')
}
}
}
function ref(value) {
if (isRef(value)) return value
return new RefImpl(value)
}
ref内部通过getter/setter拦截value属性的读写,实现与reactive相同的依赖追踪机制。当传入对象类型时,ref会自动调用reactive将其转为响应式代理。
track与trigger依赖收集派发更新机制
依赖收集和派发更新是响应式系统的核心。Vue3使用WeakMap和Set数据结构建立”目标对象 -> 属性 -> 依赖集合”的三层映射关系:
// 依赖收集与派发更新
const targetMap = new WeakMap() // target -> Map>
let activeEffect = null // 当前正在执行的effect
function effect(fn) {
activeEffect = fn
fn() // 执行函数,触发依赖收集
activeEffect = null
}
function track(target, key) {
if (!activeEffect) return
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()))
}
// 将当前effect加入依赖集合
dep.add(activeEffect)
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
// 遍历执行所有依赖该属性的effect
dep.forEach(effect => effect())
}
}
当组件渲染函数执行时,读取响应式数据触发track,将渲染函数作为effect记录到依赖集合。当数据被修改时,触发trigger,遍历执行所有关联的effect函数,驱动组件重新渲染。
computed与watch的响应式依赖实现
computed基于effect实现,但增加了缓存机制和脏值标记。computed返回的值只在依赖发生变化时重新计算,否则返回缓存结果:
// computed 简化实现
class ComputedRefImpl {
constructor(getter) {
this._getter = getter
this._dirty = true // 脏值标记
this._value = undefined
this.__v_isRef = true
}
get value() {
if (this._dirty) {
// 在effect上下文中执行getter,自动收集依赖
this._value = effect(this._getter)
this._dirty = false
}
track(this, 'value')
return this._value
}
}
function computed(getter) {
return new ComputedRefImpl(getter)
}
watch函数通过effect的scheduler选项实现,当依赖变化时不立即执行回调,而是将回调放入调度队列,在nextTick中异步执行:
// watch 简化实现
function watch(source, callback) {
let oldValue
const effectFn = effect(() => {
// 访问source触发依赖收集
return typeof source === 'function' ? source() : source.value
}, {
scheduler(fn) {
// 依赖变化时,不立即执行,而是调用回调
const newValue = fn()
callback(newValue, oldValue)
oldValue = newValue
}
})
oldValue = effectFn()
}
响应式系统的性能优化策略
Vue3响应式系统在性能方面做了多项优化:懒代理避免初始化时递归遍历深层对象,只在访问时按需代理;缓存代理通过WeakMap避免同一对象被多次代理;批量更新通过microtask队列将多次数据修改合并为一次渲染;标记删除通过markRaw跳过不需要响应式的对象。
实际开发中的性能优化建议:大列表使用shallowRef避免深层代理开销;只读数据使用readonly或markRaw跳过响应式处理;频繁修改的数据使用computed缓存中间计算结果;避免在模板中直接调用函数产生不必要的依赖收集。
Vue3响应式系统从设计到实现都体现了对性能和开发体验的双重关注。Proxy替代Object.defineProperty解决了深层代理和数组监听的历史痛点,Composition API配合reactive和ref提供了灵活的响应式数据组织方式。理解track/trigger的依赖追踪机制和effect调度策略,有助于在复杂组件中写出更高效的响应式代码。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3compositionapi-xiang-ying-shi-xi-tong-yuan-li-yu/