Vue3 手写响应原理
一、概念
- Vue 3 响应式核心原理
Vue 3 抛弃 Vue 2 的Object.defineProperty,基于Proxy + Reflect实现响应式,核心逻辑:
- 拦截操作:通过 Proxy 拦截对象的get(读)/set(写)操作;
- 依赖收集:读取响应式数据时(get),记录 "副作用函数 - 数据" 的映射关系;
- 触发更新:修改响应式数据时(set),执行该数据关联的所有副作用函数。
- Proxy和Object.defineProperty对比
| 维度 | Object.defineProperty(Vue 2) | Proxy + Reflect(Vue 3 ) |
|---|---|---|
| 支持类型 | 仅对象 / 数组(需特殊处理) | 支持所有复杂类型(Object/Array/Map 等) |
| 拦截能力 | 仅拦截属性读写 | 拦截 13 种操作(读写 / 删除 / 遍历等) |
| 深层响应式 | 递归到底(初始化性能差) | 惰性拦截(访问时创建代理,性能优) |
| 新增 / 删除属性 | 无法拦截(set, delete) | api原生支持 |
- Reflect 的核心作用
- 标准化操作行为:将obj[key]封装为Reflect.get(obj, key),返回值更规范;
- 保留上下文:保证 Proxy 拦截时this指向原始对象(原始对象get/set访问this指向);
- 与 Proxy 一一对应:13 种拦截方法完美适配。
二、手写代码核心逻辑
js
class Depend {
constructor(){
this.effectFns = new Set()
}
track(){
this.effectFns.add(dependCollection.dependCallback)
dependCollection.dependCallback = null
}
trigger(){
if(this.effectFns.size <= 0) return
for(let fn of this.effectFns){
fn?.()
}
}
}
class CollectionOfDepend {
_dependWeakMap = null
dependCallback = null
constructor(){
this._dependWeakMap = new WeakMap()
}
trackDepend(target, key){
let dependMap = this._dependWeakMap.get(target)
if(!dependMap){
dependMap = new Map()
this._dependWeakMap.set(target, dependMap)
}
let depend = dependMap.get(key)
if(!depend){
depend = new Depend()
dependMap.set(key, depend)
}
depend.track()
}
trigger(target, key){
this._dependWeakMap.get(target)?.get?.(key)?.trigger?.()
}
}
const dependCollection = new CollectionOfDepend()
const isDeepMap = (value) => {
if(value === null) return false
return (typeof value === 'object') || Array.isArray(value)
}
const reactive = (proxyObj) => {
return new Proxy(proxyObj, {
get(target, key, receiver){
const value = Reflect.get(target, key, receiver)
// 对象继续深层遍历
if(isDeepMap(value)){
return reactive(value)
}
dependCollection.trackDepend(target, key)
return value
},
set(target, key, value , receiver){
const oldValue = Reflect.get(target, key)
Reflect.set(target, key, value, receiver)
// value没有改变时不触发
if(oldValue === value) return
dependCollection.trigger(target, key)
}
})
}
const userInfo = reactive({
name: '刘德华',
age: 18,
sex: '男',
friend: {
name: '小孔'
}
})
const watchEffect = (initinalFn) => {
dependCollection.dependCallback = typeof initinalFn === 'function' ? initinalFn : () => {}
try {
initinalFn()
} catch (error) {
throw new Error(error)
} finally {
dependCollection.dependCallback = null
}
}
watchEffect(() => {
console.log('watchEffect1', userInfo.name)
})
watchEffect(() => {
console.log('watchEffect2', userInfo.age)
})
watchEffect(() => {
console.log('watchEffect3', userInfo.name, userInfo.age)
})
watchEffect(() => {
console.log('watchEffect4', userInfo.friend.name)
})
// userInfo.name = '张学友'
userInfo.friend.name = '小明'
// 打印结果 --------------------------------------------
// watchEffect1 刘德华
// watchEffect2 18
// watchEffect3 刘德华 18
// watchEffect4 小孔
// watchEffect4 小明
三、总结
Vue3 抛弃了 Vue2 中的旧响应系统,通过 Proxy 对数据进行劫持,并且通过发布订阅模式对每个需要监听的对象进行了依赖收集 (get属性),再使用了Proxy 的 set 属性来触发依赖,相比使用之前 defineProperty并递归遍历对象,性能得到了极大的提升。