Vue3 响应式原理与优势
Proxy、Reflect 和懒代理的实现机制
问题
Vue3 的响应式系统是如何实现的?相比 Vue2 有哪些优势?
解答
Vue2 的问题
Vue2 使用 Object.defineProperty,存在以下限制:
// Vue2 无法检测的情况
const obj = { a: 1 }
// 1. 新增属性无法检测
obj.b = 2 // 不触发更新,需要 Vue.set()
// 2. 数组索引修改无法检测
arr[0] = 'new' // 不触发更新
// 3. 初始化时必须递归遍历所有属性
function defineReactive(obj) {
Object.keys(obj).forEach(key => {
let value = obj[key]
// 递归处理嵌套对象(性能开销大)
if (typeof value === 'object') {
defineReactive(value)
}
Object.defineProperty(obj, key, {
get() { /* 收集依赖 */ return value },
set(newVal) { /* 触发更新 */ value = newVal }
})
})
}
Vue3 使用 Proxy
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key) // 收集依赖
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key) // 触发更新
return result
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
trigger(target, key)
return result
}
})
}
const state = reactive({ a: 1 })
state.b = 2 // ✅ 新增属性可检测
delete state.a // ✅ 删除属性可检测
为什么用 Reflect
const obj = {
_name: 'vue',
get name() {
return this._name
}
}
const proxy = new Proxy(obj, {
get(target, key, receiver) {
console.log('get:', key)
// 错误写法:target[key] 会导致 this 指向原对象
// return target[key]
// 正确写法:Reflect.get 保证 this 指向 proxy
return Reflect.get(target, key, receiver)
}
})
proxy.name
// 使用 Reflect:get: name, get: _name(两次拦截)
// 不用 Reflect:get: name(只拦截一次,_name 的访问丢失)
懒代理(惰性代理)
Vue3 不在初始化时递归代理所有嵌套对象,而是在访问时才代理:
const reactiveMap = new WeakMap()
function reactive(target) {
// 已代理过的直接返回
if (reactiveMap.has(target)) {
return reactiveMap.get(target)
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
// 懒代理:访问时才对嵌套对象进行代理
if (typeof result === 'object' && result !== null) {
return reactive(result)
}
return result
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key)
return result
}
})
reactiveMap.set(target, proxy)
return proxy
}
// 示例
const state = reactive({
user: {
profile: {
name: 'vue'
}
}
})
// 初始化时只代理最外层
// 访问 state.user 时才代理 user 对象
// 访问 state.user.profile 时才代理 profile 对象
简化的依赖收集与触发
let activeEffect = null
const targetMap = new WeakMap()
// 收集依赖
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
deps.add(activeEffect)
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const deps = depsMap.get(key)
if (deps) {
deps.forEach(effect => effect())
}
}
// effect 函数
function effect(fn) {
activeEffect = fn
fn()
activeEffect = null
}
// 使用
const state = reactive({ count: 0 })
effect(() => {
console.log('count:', state.count)
})
state.count++ // 自动打印 "count: 1"
关键点
- Proxy 代理整个对象:可拦截新增、删除属性,支持数组索引修改
- Reflect 保证正确的 this 指向:getter 中的 this 指向代理对象而非原对象
- 懒代理提升性能:嵌套对象在访问时才代理,避免初始化时的递归开销
- WeakMap 存储依赖:target → Map(key → Set(effects)),自动垃圾回收
- 不支持 IE:Proxy 无法 polyfill,这是 Vue3 放弃 IE 的原因
目录