Vue3 的响应式系统源码级数组方法的重写与优化原理
字数 1065 2025-11-21 22:42:13

Vue3 的响应式系统源码级数组方法的重写与优化原理

描述
Vue3 的响应式系统通过 Proxy 代理对象实现依赖收集与更新触发,但数组操作(如 push、pop、splice 等)需要特殊处理。由于这些方法会修改数组长度或内容,需重写原生数组方法,确保能正确触发依赖更新,同时避免不必要的副作用。

解题过程

  1. 问题分析

    • 原生数组方法(如 push)直接操作数组时,Proxy 只能拦截 setdeleteProperty 操作,但无法感知方法内部对数组长度的修改。
    • 例如,arr.push(1) 会触发两次更新:一次是修改 arr[length] 的值,另一次是修改 length 属性。若直接使用原生方法,会导致重复触发依赖。
  2. 重写数组方法的核心逻辑

    • 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, ...) 通知更新。
  3. 源码级实现详解

    // 重写示例: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
      })
    })
    
  4. 优化策略:避免重复触发

    • 重写的方法内部会暂停依赖收集,确保仅在方法执行完成后触发一次更新。
    • 通过 __v_raw 获取原始数组的依赖管理器(ob.dep),直接通知关联的 effect 执行。
  5. 与 Proxy 拦截器的协同

    • 数组的响应式代理在 baseHandlers.ts 中通过 createArrayInstrumentations() 注入重写的方法。
    • 当代理数组调用 push 时,实际执行的是重写后的方法,而非直接触发 Proxy 的 set 拦截。

总结
Vue3 通过重写数组方法,结合暂停/恢复依赖收集的机制,精准控制数组变更的更新触发,既保证了响应式系统的正确性,又避免了性能浪费。这一设计体现了响应式系统对 JavaScript 内置对象特殊行为的深度适配。

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, ...) 通知更新。 源码级实现详解 优化策略:避免重复触发 重写的方法内部会 暂停依赖收集 ,确保仅在方法执行完成后触发一次更新。 通过 __v_raw 获取原始数组的依赖管理器( ob.dep ),直接通知关联的 effect 执行。 与 Proxy 拦截器的协同 数组的响应式代理在 baseHandlers.ts 中通过 createArrayInstrumentations() 注入重写的方法。 当代理数组调用 push 时,实际执行的是重写后的方法,而非直接触发 Proxy 的 set 拦截。 总结 Vue3 通过重写数组方法,结合暂停/恢复依赖收集的机制,精准控制数组变更的更新触发,既保证了响应式系统的正确性,又避免了性能浪费。这一设计体现了响应式系统对 JavaScript 内置对象特殊行为的深度适配。