【vue2源码】vue2 改写数组的方法

vue2 响应式缺陷

vue2 的响应式由 Object.defineProperty 完成,这个方法是为对象设计的,数组肯定是无法使用。

  1. Object.defineProperty 的缺陷
    Vue 2 使用 Object.defineProperty 实现响应式,但该 API:
  • 无法监听数组索引变化(arr[0] = x)
  • 无法检测 length 属性修改、
  1. 性能权衡
    若要对数组索引实现响应式,需要:
  • 遍历所有索引并用 defineProperty 包装
  • 对大规模数组会造成严重性能问题

如何处理数组的方法

源码地址:vue\src\core\observer\index.ts

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
export class Observer {
  dep: Dep
  vmCount: number // number of vms that have this object as root $data
  /* 
    value: 需要转为响应式的目标对象/数组
    shallow: 是否浅层观察(不递归子属性)
    mock: 是否为测试环境模拟
  */
  constructor(public value: any, public shallow = false, public mock = false) {
    // this.value = value
    // # 创建依赖收集器
    this.dep = mock ? mockDep : new Dep()
    // # 初始化 Vue 实例计数器
    this.vmCount = 0
    // # 给value添加__ob__属性指向当前Observer实例,__ob__: 标记该对象已被观察,避免重复处理
    // # def(): Vue 的工具函数,等价于 Object.defineProperty
    def(value, '__ob__', this)

    if (isArray(value)) {
      if (!mock) {
        // # 原型链拦截(现代浏览器):修改 __proto__ 指向重写后的原型
        if (hasProto) {
          /* eslint-disable no-proto */
          // # 修改数组原型链
          ;(value as any).__proto__ = arrayMethods
          /* eslint-enable no-proto */
        } else {
          // # 方法拷贝(兼容旧环境):直接在数组上定义重写的方法
          for (let i = 0, l = arrayKeys.length; i < l; i++) {
            const key = arrayKeys[i]
            // # 降级方案:直接定义方法
            def(value, key, arrayMethods[key])
          }
        }
      }
      if (!shallow) {
        // # 观察数组元素,调用 observe(),实现深层响应式
        this.observeArray(value)
      }
    } else {
      /**
       * Walk through all properties and convert them into
       * getter/setters. This method should only be called when
       * value type is Object.
       */
      // # 对象的响应式处理:递归实现
      const keys = Object.keys(value)
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
      }
    }
  }

vue2 改写数组响应式的方法

源码路径:vue\src\core\observer\array.ts

// # 数组改写方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  // # 缓存原始方法(如 Array.prototype.push)
  const original = arrayProto[method]

  // # 定义重写后的方法到 arrayMethods 对象上
  def(arrayMethods, method, function mutator(...args) {
    // ---- 步骤 1:执行原始方法 ----
    const result = original.apply(this, args)
    // ---- 步骤 2:获取关联的 Observer 实例 ----
    const ob = this.__ob__
    // ---- 步骤 3:处理新增元素(仅 push/unshift/splice 可能添加新元素) ----
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args     // # push/unshift 的参数是新增元素
        break
      case 'splice':
        inserted = args.slice(2) // # splice 的第三个参数起是新增元素
        break
    }

    // # 如果存在新增元素,递归观察它们
    if (inserted) ob.observeArray(inserted)
    // notify change
    // ---- 步骤 4:触发依赖更新 ----
    if (__DEV__) {
      // # 开发环境下传递额外调试信息
      ob.dep.notify({
        type: TriggerOpTypes.ARRAY_MUTATION,
        target: this,
        key: method
      })
    } else {
      // # 生产环境直接通知更新
      ob.dep.notify()
    }
    // # 返回原始方法的执行结果
    return result
  })
})
posted @ 2025-05-23 16:11  wanglei1900  阅读(12)  评论(0)    收藏  举报