详解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监听数组变化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
vue2利用html2canvas+jspdf动态生成多页PDF方式
利用vue2结合html2canvas和jspdf,可以实现前端页面内容导出为PDF的功能,首先需要安装相关依赖,使用html2canvas将指定div内容捕捉为图像,再通过jspdf将图像转换为PDF2024-09-09vue中keep-alive、activated的探讨和使用详解
这篇文章主要介绍了vue中keep-alive、activated的探讨和使用详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2020-07-07
最新评论