Vue3 的响应式系统源码级数组的响应式处理与索引修改优化原理
字数 1274 2025-11-23 09:59:01
Vue3 的响应式系统源码级数组的响应式处理与索引修改优化原理
描述
Vue3 的响应式系统通过 Proxy 代理对象实现数据响应式,但对数组的处理存在特殊优化。数组的 length 属性修改、索引直接设置(如 arr[0] = 1)及原生方法(如 push)的调用均需触发依赖更新。源码通过重写数组方法并配合 Proxy 的 set 和 get 拦截器,实现对数组操作的精准依赖收集与更新触发。
解题过程
-
数组的响应式初始化
- 当
reactive(arr)执行时,会通过createReactiveObject函数创建 Proxy 代理对象。 - 数组与普通对象的区别在于其
__v_isReactive标记和内部方法处理逻辑。Vue3 会对数组的原生方法(如push、pop)进行重写,以确保这些方法能正确触发依赖更新。
- 当
-
数组方法的拦截与重写
- 在
baseHandlers.ts中,mutableHandlers的get拦截器会判断目标是否为数组。若访问的是数组方法(如push),则返回重写后的方法。 - 重写逻辑(在
arrayInstrumentations.ts中)核心步骤:- 通过
pauseTracking()暂停依赖收集,避免方法执行过程中访问length等属性产生冗余依赖。 - 调用原始方法(如
Array.prototype.push)并记录结果。 - 通过
resetTracking()恢复依赖收集,并手动调用trigger触发更新。
- 通过
- 例如
push的重写会触发length变更的更新:const arrayInstrumentations: Record<string, Function> = { push(...args: any[]) { pauseTracking() // 暂停依赖收集 const res = Array.prototype.push.apply(this, args) resetTracking() // 恢复 trigger(this, TriggerOpTypes.ADD, 'length') // 手动触发更新 return res } }
- 在
-
索引操作的拦截(set 拦截器)
- 当执行
arr[index] = value时,Proxy 的set拦截器会判断操作类型:- 若索引
key为数字且key >= target.length,视为ADD操作(新增元素)。 - 否则视为
SET操作(修改现有元素)。
- 若索引
- 例如:
function set(target: any, key: string | symbol, value: any) { const type = Number(key) < target.length ? TriggerOpTypes.SET : TriggerOpTypes.ADD const res = Reflect.set(target, key, value) trigger(target, type, key) // 触发依赖更新 return res } - 注意:直接设置索引可能同时影响
length(如新增元素时),需通过ADD类型通知更新。
- 当执行
-
length 属性的依赖关联
- 数组的
length属性与元素索引存在隐式关联。例如,设置arr.length = 0会删除元素,需触发DELETE类型的更新。 - 在
set拦截器中,若key为'length',会对比新旧长度,对删除的索引触发DELETE操作:if (key === 'length' && target.length > oldLength) { for (let i = oldLength; i < value; i++) { trigger(target, TriggerOpTypes.DELETE, i.toString()) } }
- 数组的
-
性能优化:避免冗余触发
- 重写数组方法时,通过
pauseTracking避免内部访问属性产生临时依赖。 - 对连续操作(如多次
push)合并触发更新,减少重复渲染。
- 重写数组方法时,通过
总结
Vue3 通过组合 Proxy 拦截器与重写数组方法,精准识别数组操作的类型(ADD/SET/DELETE),并关联 length 属性的依赖管理,既保证了响应式的一致性,又通过暂停依赖收集等优化减少性能开销。