一、数组的变更检测
之前我们在学习深入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就会尽量的复用要删除的这个节点,把它直接移动到需要增加的位置,这样的话我就不需要重建一个节点,我只用将要删除的这个节点即可。