详解Vue 如何监听Array的变化

2020-06-14 05:57:45易采站长站整理

回忆

在上一篇Vue响应式原理-理解Observer、Dep、Watcher简单讲解了Observer、Dep、Watcher三者的关系。

在Observer的伪代码中我们模拟了如下代码:


class Observer {
constructor() {
// 响应式绑定数据通过方法
observe(this.data);
}
}

export function observe (data) {
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
// 将data中我们定义的每个属性进行响应式绑定
defineReactive(obj, keys[i]);
}
}

export function defineReactive () {
// ...省略 Object.defineProperty get-set
}

今天我们就进一步了解Observer里还做了什么事。

Array的变化如何监听?

data 中的数据如果是一个数组怎么办?我们发现Object.defineProperty对数组进行响应式化是有缺陷的。

虽然我们可以监听到索引的改变。


function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('我被读了,我要不要做点什么好?');
return val;
},
set: newVal => {
if (val === newVal) {
return;
}
val = newVal;
console.log("数据被改变了,我要渲染到页面上去!");
}
})
}

let data = [1];

// 对数组key进行监听
defineReactive(data, 0, 1);
console.log(data[0]); // 我被读了,我要不要做点什么好?
data[0] = 2; // 数据被改变了,我要渲染到页面上去!

但是defineProperty不能检测到数组长度的变化,准确的说是通过改变length而增加的长度不能监测到。这种情况无法触发任何改变。


data.length = 0; // 控制台没有任何输出

而且监听数组所有索引的的代价也比较高,综合一些其他因素,Vue用了另一个方案来处理。

首先我们的observe需要改造一下,单独加一个数组的处理。


// 将data中我们定义的每个属性进行响应式绑定
export function observe (data) {
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
// 如果是数组
if (Array.isArray(keys[i])) {
observeArray(keys[i]);
} else {
// 如果是对象
defineReactive(obj, keys[i]);
}
}
}

// 数组的处理
export function observeArray () {
// ...省略
}

那接下来我们就应该考虑下Array变化如何监听?

Vue 中对这个数组问题的解决方案非常的简单粗暴,就是对能够改变数组的方法做了一些手脚。