人人都会AI编程

数组响应式的特殊处理

更新时间:2026-07-09

Vue 2 的响应式系统基于 Object.defineProperty 实现,但这个 API 有一个天然的短板:它无法直接劫持数组的索引访问和 length 变化。简单说,你无法用 Object.defineProperty 监听 arr[0] = 'new value' 这种赋值操作,也没法在 arr.length = 0 清空数组时触发响应。

但真实的业务代码里,数组操作无处不在。为了解决这个问题,Vue 2 没有去硬扛 Object.defineProperty 的底层限制,而是换了一个务实的思路:重写数组的七个变更方法

拦截数组方法的实现思路

Vue 2 在观测一个数组时,不会直接使用数组原本的 proto,而是将它指向一个经过“包装”的原型对象。这个对象继承自 Array.prototype,但覆盖了以下七个会改变原数组的方法:

  • push / pop
  • shift / unshift
  • splice
  • sort
  • reverse

当你在一个响应式数组上调用 arr.push(newItem) 时,实际执行的是 Vue 重写后的方法,它做了两件事:

  1. 调用原生数组方法,完成数组的增删改操作。
  2. 手动触发依赖通知,告知 Vue 数据变了,需要更新视图。

同时,对于 pushunshiftsplice 这三个会往数组中新增元素的方法,Vue 还会对新加入的元素再次执行观测(调用 observe),让这些新元素也变成响应式的。

这意味着什么?
你日常使用数组时,只要是通过这七个方法修改数组,就能正常触发视图更新。比如:

data() {
  return {
    items: ['a', 'b', 'c']
  }
}

// 以下操作都会触发视图更新
this.items.push('d')
this.items.pop()
this.items.splice(1, 1, 'x')
this.items.sort()

大多数业务场景下,你几乎感受不到这些内部“补丁”的存在——因为大家本来也是用这些方法来增删元素的。

仍然无法自动响应的操作

既然是“补丁”,就无法覆盖所有自改数组的方式。以下两种直接修改数组索引或 length 的操作,Vue 2 无法自动检测到

// 1. 直接通过索引修改元素
this.items[0] = 'new value'   // 不会触发更新

// 2. 直接修改数组长度
this.items.length = 0         // 不会触发更新

这并不是 Vue 2 的疏忽,而是 Object.defineProperty 的能力边界。Vue 额外提供了两个弥补手段:

  • Vue.set(array, index, value):给数组指定索引设置新值,并触发响应。
  • this.$set(array, index, value):组件内等效方法。
  • 对于 items.length = 0 清空,官方推荐使用 this.items.splice(0) 代替。

此外,对于数组中对象的属性修改,Vue 2 是可以深度监听的,因为对象属性走的是正常的 Object.defineProperty 流程:

this.items[0].name = 'Alice'  // 可以触发更新,前提是 items[0] 是响应式对象

与 Vue 3 的对比

到了 Vue 3,基于 Proxy 的响应式系统从根本上解决了这个问题——Proxy 天然能拦截索引赋值和 length 修改,因此不再需要重写数组方法,也无需 Vue.set。这部分在第 4.2 节会展开。

一句话总结:Vue 2 对数组的响应式处理是“方法拦截 + 手动通知”的补丁方案,覆盖了日常 90% 的场景,但直接通过索引修改和修改长度仍然需要额外注意。理解了这一点,你就能避免“明明改了数组,页面却纹丝不动”的困惑。