Vue2如何监听数组的变化?(附相关面试题)

前言

众所周知,vue2的响应式原理是 数据劫持结合发布订阅模式.具体是通过Object.defineProperty()方法来劫持各个属性的getter和setter,从而能够监听到数据的变化。,但是Object.defineProperty不能监听数组的变化那么vue2是怎么实现数组响应式的呢?而且在日常开发中,我们会发现不能直接修改数组的length长度,也不能通过数组下标的方式修改数据,比如:arr[0]=123这种方式不能响应式。

那么,vue2是如何实现的呢?

Vue2内部通过重写数组的原型方法来监听数组的变动。

具体来说,Vue2首先获取到数组的原型,然后创建一个新的对象继承自该原型,接着将这个新对象的原型上的七个能够修改数组自身的方法(push、pop、shift、unshift、splice、sort、reverse)进行重写。这些方法在执行时,除了执行其原有的逻辑之外,还会触发视图更新。

以下是一个简化的重写示例:

// 获取数组的原型
const arrayProto = Array.prototype;
// 创建一个新的对象,该对象的原型就是arrayProto
const arrayMethods = Object.create(arrayProto);

// 需要被改写的方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];

methodsToPatch.forEach(function(method) {
  // 缓存原始方法
  const original = arrayProto[method];
  // 定义新的方法
  Object.defineProperty(arrayMethods, method, {
    value: function mutator(...args) {
      // 先执行原始方法
      const result = original.apply(this, args);
      // 获取数组对象的__ob__属性,__ob__是每个响应式对象都有的一个属性,指向该对象的Observer实例
      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);
      // 通知变更
      ob.dep.notify();
      return result;
    },
    configurable: true,
    enumerable: false,
    writable: true
  });
});

如何使用

在初始化响应式数据时,Vue会判断一个对象是否是数组。如果是数组,Vue则会将这个数组的原型指向上面提到的arrayMethods,从而使得这个数组调用7个修改自身的方法时,能够触发视图的更新。这一过程主要是在Observer类的实例化过程中完成的。

if (Array.isArray(value)) {
  if (hasProto) {
    protoAugment(value, arrayMethods);
  } else {
    copyAugment(value, arrayMethods, arrayKeys);
  }
  this.observeArray(value);
}

小结

通过这种方式,Vue 2可以监测到数组的变化并作出响应。这种方法虽然巧妙,但有其局限性,比如直接通过索引设置数组元素的值或修改数组长度等操作,是无法被检测到的。Vue 3中采用了Proxy代替了这种实现方式,能够更好地解决这些问题。

解决方式

虽然Object.defineProperty本身无法拦截数组索引的直接修改或数组长度的变化,Vue 2提供了几种方法来解决这个限制,确保开发者仍然可以以响应式的方式更新数组:

使用Vue.setvm.$set

为了解决直接通过索引修改数组元素的问题,Vue 2引入了Vue.set函数和vm.$set实例方法。这两个方法允许开发者在指定索引处插入或替换数组元素,同时保证变化是响应式的。

// 假设有一个Vue组件的data如下:
data() {
  return {
    fruits: ['apple', 'banana', 'cherry']
  };
},
methods: {
  updateFruit() {
    this.$set(this.fruits, 1, 'orange'); // 将索引1处的'banana'替换为'orange'
  }
}
使用数组的splice方法

另一个解决方案是使用数组的splice方法。splice不仅可以在数组中添加/删除项目,而且由于Vue重写了这个方法,使用它进行的任何操作都会触发视图更新。

updateFruit() {
  this.fruits.splice(1, 1, 'orange'); // 同样的效果,替换操作
}

响应式系统的限制与规避策略

虽然Vue的响应式系统提供了强大的数据绑定能力,但了解其内部工作原理和限制对于开发高效、可维护的Vue应用至关重要。通过正确地使用Vue提供的工具和方法(如Vue.setvm.$setsplice),开发者可以确保即使是那些原生JavaScript限制下不可直接侦测的变化,也能被Vue的响应式系统捕获并正确地更新视图。

一个面试题:Object.defineProperty如何监听数组?为什么无法获取数组的变化?

Object.defineProperty 本身并不直接用于监听数组的变化,因为它是设计来劫持和监听对象属性的读取和写入操作的。当我们使用 Object.defineProperty 对对象的属性进行劫持时,我们实际上是在设置属性的 getter 和 setter,这样每当属性被访问或修改时,我们就可以执行自定义的逻辑,比如通知视图进行更新。然而,当应用到数组上时,存在几个核心限制使得 Object.defineProperty 无法有效地监听数组的变化:

1. 数组索引的修改

当通过索引直接修改数组(如 arr[0] = 'new value')时,这实际上是一个属性赋值操作。虽然理论上可以对数组的每个索引使用 Object.defineProperty 来监听变化,但这在实践中是不可行的,因为:

  • 性能问题:数组可能非常大,为每个索引设置 getter 和 setter 会极大地影响性能。
  • 动态性问题:数组长度是动态变化的,每次数组变化时都需要重新为新的索引设置劫持,这在技术上是复杂且低效的。

2. 修改数组长度

直接修改数组的 length 属性(例如,通过设置 arr.length = 0 来清空数组),这种操作同样无法被 Object.defineProperty 直接侦测到。这是因为 length 属性的变化不会触发索引属性的 setter。

3. 使用数组方法

数组的方法(如 pushpopsplice 等)可以修改数组的内容或结构。这些操作不仅改变数组元素,有时还会改变数组的长度。Object.defineProperty 无法直接拦截这些方法调用,因为它们是数组原型上的方法,而不是数组实例上的直接属性。

Vue 2 如何实现数组的响应式

正因为上述限制,Vue 2 选择了一种不同的方式来实现对数组的响应式监听:

  • 重写数组方法:Vue 2 通过修改数组实例的原型,将数组的一些方法(如 pushpop 等)重写为可以触发视图更新的版本。当这些重写的方法被调用时,Vue 可以捕获到数组的变动并触发相应的更新。

总结来说,Object.defineProperty 由于其内在的机制和限制,并不能直接用于有效监听数组的变化。Vue 2 通过一种巧妙的方式绕过了这些限制,能够实现对数组操作的响应式更新。

  • 27
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

接着奏乐接着舞。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值