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就会尽量的复用要删除的这个节点,把它直接移动到需要增加的位置,这样的话我就不需要重建一个节点,我只用将要删除的这个节点即可。

相关推荐
活宝小娜1 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点1 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow1 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o1 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
----云烟----2 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024062 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic2 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā2 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
向宇it2 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康2 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud