Vue3 的响应式系统源码级数组方法的重写与优化原理
字数 1065 2025-11-21 22:42:13
Vue3 的响应式系统源码级数组方法的重写与优化原理
描述
Vue3 的响应式系统通过 Proxy 代理对象实现依赖收集与更新触发,但数组操作(如 push、pop、splice 等)需要特殊处理。由于这些方法会修改数组长度或内容,需重写原生数组方法,确保能正确触发依赖更新,同时避免不必要的副作用。
解题过程
-
问题分析
- 原生数组方法(如
push)直接操作数组时,Proxy 只能拦截set或deleteProperty操作,但无法感知方法内部对数组长度的修改。 - 例如,
arr.push(1)会触发两次更新:一次是修改arr[length]的值,另一次是修改length属性。若直接使用原生方法,会导致重复触发依赖。
- 原生数组方法(如
-
重写数组方法的核心逻辑
- Vue3 在
packages/reactivity/src/array.ts中重写了 7 个会改变原数组的方法:
push,pop,shift,unshift,splice,sort,reverse。 - 重写逻辑分为三步:
a. 暂停依赖收集:调用pauseTracking()避免方法执行过程中重复收集依赖。
b. 执行原生方法:通过Array.prototype.method.call(target, ...args)执行操作。
c. 恢复依赖收集并触发更新:调用resetTracking()恢复收集,并通过trigger(target, TriggerOpTypes.ARRAY_METHOD, ...)通知更新。
- Vue3 在
-
源码级实现详解
// 重写示例:push 方法 const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) // 创建纯净原型链 ;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => { const original = arrayProto[method] def(arrayMethods, method, function mutator(...args) { // 暂停当前活跃的 effect 的依赖收集 pauseTracking() // 执行原生方法并获取结果 const result = original.apply(this, args) // 恢复依赖收集 resetTracking() // 通过响应式对象的 __v_raw 属性获取原始对象 const ob = this.__v_raw // 触发数组更新,明确操作类型(如 ADD、DELETE) ob.dep.notify({ type: TriggerOpTypes.ARRAY_METHOD, target: this, key: method }) return result }) }) -
优化策略:避免重复触发
- 重写的方法内部会暂停依赖收集,确保仅在方法执行完成后触发一次更新。
- 通过
__v_raw获取原始数组的依赖管理器(ob.dep),直接通知关联的 effect 执行。
-
与 Proxy 拦截器的协同
- 数组的响应式代理在
baseHandlers.ts中通过createArrayInstrumentations()注入重写的方法。 - 当代理数组调用
push时,实际执行的是重写后的方法,而非直接触发 Proxy 的set拦截。
- 数组的响应式代理在
总结
Vue3 通过重写数组方法,结合暂停/恢复依赖收集的机制,精准控制数组变更的更新触发,既保证了响应式系统的正确性,又避免了性能浪费。这一设计体现了响应式系统对 JavaScript 内置对象特殊行为的深度适配。