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()都能够正常的监听到。

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()方法的缺点。

vm.list.push(7); // 数据的响应式获取

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

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()不能监听到数组内部数据变化的问题。

// 定义哪些数组方法是操作的原数组
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就会尽量的复用要删除的这个节点,把它直接移动到需要增加的位置,这样的话我就不需要重建一个节点,我只用将要删除的这个节点即可。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

️不倒翁

你的鼓励就是我前进的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值