前言

Vue 有点了解的人都知道 Vue 的数据双向绑定,是其非常重要的特性之一。那么 Vue 是如何进行数据更新的了?又是如何追踪数据变化的了?本篇文章就以该问题为主题来聊聊 Vue 的数据响应式原理!

追踪对象类型的数据变化

关于 Vue 如何追踪数据变化在 Vue官方文档-深入响应式原理 中有详细的说明。

其实简单来说,Vue 就是通过使用 ES5 中的 Object.defineProperty 将所有绑定在 data 选项中的属性全部都转为 getter/setter。然后在 setter 中添加监控方法,当每次属性被改变时,触发改监控方法,以达到通知 watcher 重新计算及通知相关数据更新的目的!

由于使用的是 ES5 中的 Object.defineProperty 方法,这就导致 Vue 无法支持 IE8 及以下版本的浏览器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});

看过以上内容,相信大家对于对象类型的数据追踪应该有了一些初步的了解,这也就很容易理解接下来要讲的内容了。

上面我们说了,Vue 是通过将属性转为 getter/setter 来实现数据变化检测的。那么,这就要求在实例初始化时,这些属性必须已经在 data 对象上才能实现。这也就导致,Vue 无法检测到对象属性的动态添加或删除!如果需要动态的添加 data 上的相应式属性,可以使用 Vue.set(object, key, value) 实现!

追踪数组类型的数据变化

上面的内容大家就算没有看过,但是多少应该都知道一点。但是,不知道大家有没有想过:只有对象类型的数据才能使用 getter/setter 来监控数据变化。那么如果数据是数组类型的数据了,怎么进行数据监测了?

有人可能会说,数组也是对象啊,哈哈。原则上这种说法没有错,但是数组可没法定义 getter/setter。那么 Vue 是如何实现对于数组类型数据进行监控的了,详情看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);

var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];

/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];

var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// notify change
ob.dep.notify();
return result
});
});

大家看完源码后,是不是就明白了!是的,没错, Vue 中重写了数组常用的 push, pop, shift, unshift, splice, sort, reverse 方法!并且在这些方法中添加了数据监控钩子!当数组使用这些方法,进行数据变化时,就会触发监控函数,从而达到监控数组数据变化的目的!

但是,很明显的,Vue 只是重写了以上这些方法,那么,当我们使用索引的方式动态添加,或改变数组数据,Vue 就无法监控数据变化了

1
2
3
4
5
6
7
8
9
10
11
data() {
arr: [1, 2, 3, 4],
},

mouted() {
// 动态添加元素
this.arr[4] = 5;

// 动态修改元素
this.arr[1] = 6;
}

所以,在日常使用中,我们要尽量避免使用数组索引的方式去改变数据,如果有需要的话,可以使用 vue.set 方法进行数据修改!

结尾

到这里,文章基本也就结束,本文主要是简单分析了一下 Vue 中的数据监控,当然主要部分还是数组部分的数据监控实现,毕竟对于对象的 getter/setter 知道的人还是比较多的,但是对于数组数据的监控方法实现,知道的人就相对少一点了!

转载说明

本文允许全文转载,转载请注明来源:
平凡公子 - vue从入门到精通之响应式原理