vue2中数组的变更检测

一、数组的变更检测

之前我们在学习深入data属性的知识中,提及到Vue2中通过Object.defineProperty()方法实现数据响应、数据绑定。但是Object.defineProperty()方法有一个缺点,就是Object.defineProperty()在数组内部数据变动的时候,不能监听到数据的变动。换句话说,也就是Object.defineProperty()并不能监听到数组内部元素的数据变动。

我们通过Object.defineProperty()方法对vm对象进行了数据绑定,现在可以通过vm.a、vm.b、vm.list的语法直接访问到data对象内部的属性值。也就是说跳过了data对象直接访问到data对象内部的属性值,这也是我们之前学习过的内容。现在这些vm.a、vm.b、vm.list = [1, 2, 3] Getter、Setter操作,Object.defineProperty()都能够正常的监听到。

javascript 复制代码
let vm  = {
  data:{
    message: 'Hello Vue!',
    a:1,
    b:2,
    list:[1,2,3,4,5]
  }
}

for (const key in vm.data) {
    Object.defineProperty(vm,key,{
      get:function(){
        console.log('响应式数据获取');
        __get__(key,vm.data[key])
        return vm.data[key];
      },
      set:function(newValue){
        console.log('响应式数据设置');
        const oldValue = vm.data[key];
        vm.data[key] = newValue;
        __set__(key,newValue,oldValue)
      }
    })
  }

vm.a; // '数据的响应式获取'
vm.b = 3; // '数据的响应式设置'
vm.list = [1, 2, 3]; // '数据的响应式设置'

但是现在出现一个问题,就是Object.defineProperty()方法没办法监听到数组内部数据变化。比如说,我们下面例子建立在上面例子的基础上进一步操作。我现在想给data对象中的list数组添加一项新元素7,此时虽然触发Getter函数,但是我们发现并没有触发Setter函数,也就是说明Object.defineProperty()方法并没有监听到数据内部的数据变化。

可能现在有人要问,为什么上面vm.list = [1, 2, 3];的方式就能够触发Setter函数呢?而vm.list.push(7);就不能了呢?因为vm.list = [1, 2, 3];的方式是更改数组,改变了原来vm.list属性保存的数组引用,换句话说就是返回了新的数组引用;vm.list.push()并没有改变数组的引用,仅仅只是改变数组内部元素。

那问题随之而来。此时操作vm.list数组中的元素,并不能够触发数据劫持中的Setter函数,如果不能够触发Setter函数,那么后期更新视图的功能就不能进行,这也正是Object.defineProrpety()方法的缺点。

javascript 复制代码
vm.list.push(7); // 数据的响应式获取

那么Vue中是如何解决这个问题的呢?首先我们需要清楚,如果通过Object.defineProperty()方法处理数据劫持,那么哪些数组方法是不能触发Setter函数的呢?其实也很简单,如果数组方法只是在原数组上操作的话,那么就不能够触发Setter函数;如果不是在原数组上操作,而是操作后返回一个新的数组,那么就可以触发Setter函数。

数组方法中在原数组上操作的方法有:push、pop、unshift、shift、splice、sort、reverse。下面的这些方法一律都是在原数组上进行操作的方法,所以触发不了Setter函数。

javascript 复制代码
vm.list.push(6);
vm.list.pop();
vm.list.unshift();
vm.list.splice(2,1); 
vm.list.sort((a, b) => a - b);
vm.list.reverse();

Vue通过对这些方法进行了一层包装,将这些不能够触发Setter函数的数组方法进行一层包装。包装的目的是:在调用这些方法的时候,不仅仅能够执行数组方法的原本逻辑,还能够进行后续的更新视图操作,因为Object.defineProperty()没有办法监听数组内部的数据变动,所以我们需要手动的进行视图更新的逻辑处理(如果你调用了这些方法)。

下面的例子中,展示了如何对这些数组方法进行包裹。通过这种方式,虽然Object.defineProperty()方法不能监听数组中数据的变化,但是如果你调用了这些数组方法。那么我不仅手动的帮你完成这些数组方法原本的逻辑,还帮你完成数据变化的视图更新逻辑,这样就解决了Object.defineProperty()不能监听到数组内部数据变化的问题。

javascript 复制代码
// 定义哪些数组方法是操作的原数组
var ARR_METHODS = ['push','pop','shift','unshift','splice','sort','reverse'];

var originArrMethods = Array.prototype, // 拿到所有数组的方法
    arrMethods = Object.create(originArrMethods); // 创建一个原型链指向数组的对象

ARR_METHODS.map(function(m){
  // 遍历所有需要重新定义的数组方法
  arrMethods[m] = function(){
    // 拿到数组的参数
    var args = Array.prototype.slice.call(arguments),
      // 执行原数组的方法
    rt = originArrMethods[m].apply(this, args);
    console.log('数组新方法执行',args);
    // 视图更新的逻辑......
  }
})

二、替换数组会影响性能吗

替换数组是否会重新渲染整个DOM列表,出现性能担忧的问题吗?这是一个需要我们讨论的问题。下面例子中,concat、slice、map数组方法在调用之后会返回新的数组,那么返回的新数组将会代替原来的list属性值,这时候需要重新渲染DOM结构,那么会影响性能吗?

答案是:不一定滴~,因为Vue在对DOM操作的时候会进行大量新旧节点比对的算法,Vue会将DOM重新渲染的程度最小化,做到已有的DOM节点最大化的复用。比如说,我现在有一个节点P,这个节点P可能不渲染用的,但是呢,我可能要删除这个节点的同时还要增加另一个节点(新增一个div元素)。那么这种情况下,Vue就会尽量的复用要删除的这个节点,把它直接移动到需要增加的位置,这样的话我就不需要重建一个节点,我只用将要删除的这个节点即可。

相关推荐
童先生16 分钟前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
lulu_gh_yu17 分钟前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
Re.不晚41 分钟前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
老秦包你会43 分钟前
Qt第三课 ----------容器类控件
开发语言·qt
aPurpleBerry44 分钟前
JS常用数组方法 reduce filter find forEach
javascript
凤枭香1 小时前
Python OpenCV 傅里叶变换
开发语言·图像处理·python·opencv
ULTRA??1 小时前
C加加中的结构化绑定(解包,折叠展开)
开发语言·c++
远望清一色1 小时前
基于MATLAB的实现垃圾分类Matlab源码
开发语言·matlab
confiself1 小时前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
ZL不懂前端1 小时前
Content Security Policy (CSP)
前端·javascript·面试