vue源码之数组重写了哪些方法为何需要重写

7个被重写的方法

在 Vue 的源代码中,数组类型重写了以下几个方法,以实现对数据变化的响应式处理:

  1. push:向数组末尾添加一个或多个元素,并返回新数组的长度。
  2. pop:删除数组的最后一个元素,并返回其值。
  3. shift:删除数组的第一个元素,并返回其值。
  4. unshift:向数组开头添加一个或多个元素,并返回新数组的长度。
  5. splice:从数组中添加或删除元素,并返回被删除的元素。
  6. sort:对数组元素进行排序并改变原数组。
  7. reverse:反转数组中的元素顺序。

这些方法被重写的目的是为了能够在对数组进行操作时,自动触发 Vue 的响应式系统,使得视图能够及时更新。这些重写的实现主要是在 Vue 的观察者(Observer)中进行处理。通过 Vue 的响应式系统,任何对数组的修改都会被捕获,从而更新相关的组件和视图。

后续源码分析都是依据 "version": "2.7.16" (vue2版本【最新版本】)和 "version": "3.5.12"【当前最新版本】 (vue3版本)分析的对比

重写的原因

  1. 数据驱动视图:Vue 的核心理念是数据驱动视图。当数据变化时,视图应当自动更新。重写数组的方法可以让 Vue 监控数组的变化,从而触发视图的更新。
  2. 响应式系统 :对于数组的常用操作(如添加、删除元素),如果不重写这些方法,默认的 JavaScript 数组操作并不会通知 Vue 进行视图更新。重写这些方法可以捕获这些变更,确保 Vue 的响应式系统能够生效。
  3. 提高性能:Vue 通过使用虚拟 DOM 和差异算法来优化 DOM 更新。重写数组的方法也确保了只有当数据变化时,才会进行更新,从而提高了应用的性能。
  4. 一致性 :通过重写数组的方法,Vue 确保了在处理数组时,所有操作都能保持一致,避免了不同类型数据处理机制的混乱。

源码体现

vue2中源码体现

ts 复制代码
const arrayProto = Array.prototype
// 创建一个新的对象 arrayMethods ,该对象继承自 Array.prototype,这样 arrayMethods 就可以使用数组原型上的所有方法
// 通过这种方式,可以在不影响原生数组的方法的情况下,给 arrayMethods 添加自定义的方法或进行其他扩展
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change 通知变化
    if (__DEV__) {
      ob.dep.notify({
        type: TriggerOpTypes.ARRAY_MUTATION,
        target: this,
        key: method
      })
    } else {
      ob.dep.notify()
    }
    return result
  })
})

vue3中源码体现

ts 复制代码
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse',
]

const patched = new WeakSet<object>()

/**
 * 定义一个响应式属性,支持数组和对象
 * @param obj 目标对象
 * @param key 属性名
 * @param val 属性值
 */
function defineReactive(obj: any, key: string, val: any) {
  // it's possible for the original object to be mutated after being defined
  // and expecting reactivity... we are covering it here because this seems to
  // be a bit more common.
  // 检查val是否为对象(排除原始数据类型),且val未被响应式处理过
  // 如果val为对象,且未被响应式处理过,则将val转为响应式对象,并添加响应式处理标记
  // 如果val为数组,则将数组方法转为响应式处理,并添加响应式处理标记
  if (isObject(val) && !isReactive(val) && !patched.has(val)) {
    const reactiveVal = reactive(val)
    if (isArray(val)) {
      methodsToPatch.forEach((m: any) => {
        val[m] = (...args: any[]) => {
          Array.prototype[m].apply(reactiveVal, args)
        }
      })
    } else {
      Object.keys(val).forEach(key => {
        try {
          defineReactiveSimple(val, key, val[key])
        } catch (e: any) {}
      })
    }
  }

  const i = obj.$
  if (i && obj === i.proxy) {
    // target is a Vue instance - define on instance.ctx
    defineReactiveSimple(i.ctx, key, val)
    i.accessCache = Object.create(null)
  } else if (isReactive(obj)) {
    obj[key] = val
  } else {
    defineReactiveSimple(obj, key, val)
  }
}

function defineReactiveSimple(obj: any, key: string, val: any) {
  // 通过 isObject(val) 检查 val 是否为对象。如果是,则调用 reactive(val) 将其转换为响应式对象;如果不是,则保持原值
  val = isObject(val) ? reactive(val) : val
  Object.defineProperty(obj, key, {
    enumerable: true, // 该属性可以被枚举
    configurable: true, // 该属性可以被修改或删除
    get() {
      track(obj, TrackOpTypes.GET, key)
      return val
    },
    set(newVal) {
      // 判断新的值 newVal 是否为对象。如果是,调用 reactive(newVal) 进行转换;如果不是,直接赋值。
      val = isObject(newVal) ? reactive(newVal) : newVal
      trigger(obj, TriggerOpTypes.SET, key, newVal)
    },
  })
}

通过重写这些方法,Vue 能够提供一个高效、灵活的响应式系统,使得前端开发更加简单和直观。

相关推荐
FungLeo6 小时前
成为全栈·React 管理后台篇·总复盘:从“接口能调通”到“后台值得使用”
前端·react.js·前端框架·项目复盘·成为全栈
Csvn8 小时前
并发模式:让渲染学会排队、插队和让路
前端
可乐鸡翅yeah_8 小时前
新手梳理:M3U8 线上问题,哪些是前端锅,哪些是后端锅
前端·ios·音视频·实时音视频·m3u8·音视频在线播放
Flynt8 小时前
Linear 用 1000 个 PR 换掉 styled-components,我写了 200 个按钮,把这笔账复现了一遍
前端·css·preact
JavaGuide9 小时前
NVIDIA 又开源了!这次给 AI Agent 加上权限管控
前端·后端
excel9 小时前
prisma 如何处理数据库竞态
前端·数据库·后端
莪_幻尘10 小时前
Skill 体检:30 个 Skill 全凭感觉?体检器先自曝了 8 个“假 0 分
前端·人工智能·llm
风骏时光牛马10 小时前
AI模型综合能力评测:性能、指令遵循与多场景实测对比
前端
Frag0ut10 小时前
Chrome与Chromium内核浏览器在Windows 11上的新特性全景解析
前端·chrome·windows·web安全·chromium·gemini ai·playready drm
hiahiahia12311 小时前
实现完整 Tool Dispatcher
开发语言·前端