从Object.defineProperty到Proxy:Vue3响应式的架构演进
Vue2使用Object.defineProperty劫持对象属性的getter/setter,存在三个硬伤:无法检测属性新增和删除、无法监听数组索引和长度变化、深层嵌套对象需要递归代理导致初始化性能差。
Vue3改用ES6 Proxy重写响应式系统,从「属性劫持」升级为「对象代理」,一次代理覆盖所有属性操作,包括新增、删除、遍历和in操作符。核心优势:懒代理(深层属性访问时才递归)和全操作拦截。
reactive函数的源码执行路径
调用reactive(obj)时,内部执行流程:
// 简化版源码逻辑
const reactiveMap = new WeakMap()
function reactive(target) {
// 1. 只代理对象类型
if (typeof target !== 'object' || target === null) return target
// 2. 已代理直接返回
const existing = reactiveMap.get(target)
if (existing) return existing
// 3. 创建Proxy代理
const proxy = new Proxy(target, mutableHandlers)
reactiveMap.set(target, proxy)
return proxy
}
核心在mutableHandlers,它定义了Proxy的四个拦截器:
- get:读取时收集依赖(track),对嵌套对象懒代理
- set:写入时触发更新(trigger),处理新增属性
- deleteProperty:删除属性时触发更新
- has:支持
in操作符的响应式
依赖收集:track函数与effect副作用栈
Vue3维护一个全局副作用栈activeEffect,当组件渲染函数或watchEffect执行时,当前effect入栈:
let activeEffect = null
const targetMap = new WeakMap() // target → Map(key → Set(effect))
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect) // 双向依赖
activeEffect.deps.push(dep) // effect也记录dep,方便清理
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
// 拷贝后遍历,防止无限循环
const effectsToRun = new Set(dep)
effectsToRun.forEach(effect => effect.run())
}
}
依赖收集是O(1)操作,触发更新时遍历该key对应的所有effect执行。WeakMap以target为key保证对象销毁时依赖自动GC。
深层代理的懒执行机制
Vue2在初始化时递归遍历整个对象树做defineProperty,大对象启动慢。Vue3的Proxy采用懒代理策略:
function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// 收集依赖
track(target, key)
// 懒代理:只有访问到嵌套对象时才递归代理
if (isObject(res)) {
return reactive(res)
}
return res
}
reactive(res)内部会检查WeakMap,已代理的不会重复创建。首次访问state.user.profile时,逐层创建Proxy代理,未访问的嵌套对象零开销。
ref与reactive的设计取舍
reactive只接受对象,基本类型值无法Proxy代理。Vue3用ref解决:
function ref(value) {
return new RefImpl(value)
}
class RefImpl {
constructor(value) {
this._value = isObject(value) ? reactive(value) : value
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
this._value = isObject(newVal) ? reactive(newVal) : newVal
triggerRefValue(this)
}
}
ref本质是一个带.value访问器的包装对象,内部对对象类型仍然调用reactive做Proxy代理。模板中自动解包(不需要写.value)是编译器层面的语法糖。
shallowReactive与性能优化场景
当确认不需要深层响应式时,用shallowReactive跳过嵌套代理:
const state = shallowReactive({
list: largeArray, // 不会递归代理数组元素
meta: bigObject // 不会递归代理meta内部属性
})
// 需要手动触发更新
state.list.push(newItem)
triggerRef(ref(state.list)) // 手动通知
适用场景:大型不可变数据(如表格数据源)、第三方库返回的对象、性能敏感的大列表渲染。配合markRaw标记跳过代理的对象,可完全避开Proxy开销。
响应式系统的设计哲学:默认安全(全代理),按需优化(shallow/markRaw)。理解底层机制后,性能调优和bug排查才能有的放矢。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-xiang-ying-shi-yuan-li-shen-du-pou-xi-proxy-dai-li-yu/