详解Vue2如何监听数组的变化

 更新时间:2024年03月20日 15:16:11   作者:接着奏乐接着舞。  
这篇文章主要来和大家详细探讨一下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.set 或 vm.$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.set、vm.$set和splice),开发者可以确保即使是那些原生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. 使用数组方法

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

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

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

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

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

到此这篇关于详解Vue2如何监听数组的变化的文章就介绍到这了,更多相关Vue2监听数组变化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue 动态生成拓扑图的示例

    vue 动态生成拓扑图的示例

    这篇文章主要介绍了vue 动态生成拓扑图的示例,帮助大家更好的理解和使用vue框架,感兴趣的朋友可以了解下
    2021-01-01
  • vue中的路由跳转tabBar图片和文字的高亮效果

    vue中的路由跳转tabBar图片和文字的高亮效果

    这篇文章主要介绍了vue中的路由跳转tabBar图片和文字的高亮效果,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • 详解vuex中mutation/action的传参方式

    详解vuex中mutation/action的传参方式

    这篇文章主要介绍了详解vuex中mutation/action的传参方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • Monaco-editor 的 JSON Schema 配置及使用介绍

    Monaco-editor 的 JSON Schema 配置及使用介绍

    这篇文章主要为大家介绍了Monaco-editor 的 JSON Schema 配置及使用介绍,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • vue2.0基于vue-cli+element-ui制作树形treeTable

    vue2.0基于vue-cli+element-ui制作树形treeTable

    这篇文章主要介绍了vue2.0基于vue-cli+element-ui制作树形treeTable,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • Vite打包项目后图片丢失的简单解决方法

    Vite打包项目后图片丢失的简单解决方法

    vue项目完成打包上线的时候很多人都会碰到静态资源找不到的情况,下面这篇文章主要给大家介绍了关于Vite打包项目后图片丢失的简单解决方法,需要的朋友可以参考下
    2023-05-05
  • Vue父组件调用子组件事件方法

    Vue父组件调用子组件事件方法

    下面小编就为大家分享一篇Vue父组件调用子组件事件方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-02-02
  • vue3中使用editor.js的详细步骤记录

    vue3中使用editor.js的详细步骤记录

    富文本编辑器作为直接与用户交互的内容输入生产工具,对大家的项目来说非常重要,下面这篇文章主要给大家介绍了关于vue3中使用editor.js的详细步骤,需要的朋友可以参考下
    2024-01-01
  • vue3封装京东商品详情页放大镜效果组件

    vue3封装京东商品详情页放大镜效果组件

    这篇文章主要为大家详细介绍了vue3封装类似京东商品详情页放大镜效果组件,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • vue实现树形菜单效果

    vue实现树形菜单效果

    这篇文章主要为大家详细介绍了vue实现树形菜单效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03

最新评论