为了深入介绍响应式系统的内部实现原理,我们花了一整节的篇幅介绍了数据(包括
data, computed,props
)如何初始化成为响应式对象的过程。有了响应式数据对象的知识,上一节的后半部分我们还在保留源码结构的基础上构建了一个以data
为数据的响应式系统,而这一节,我们继续深入响应式系统内部构建的细节,详细分析Vue
在响应式系统中对data,computed
的处理。
7.8 相关概念
在构建简易式响应式系统的时候,我们引出了几个重要的概念,他们都是响应式原理设计的核心,我们先简单回顾一下:
Observer
类,实例化一个Observer
类会通过Object.defineProperty
对数据的getter,setter
方法进行改写,在getter
阶段进行依赖的收集,在数据发生更新阶段,触发setter
方法进行依赖的更新watcher
类,实例化watcher
类相当于创建一个依赖,简单的理解是数据在哪里被使用就需要产生了一个依赖。当数据发生改变时,会通知到每个依赖进行更新,前面提到的渲染wathcer
便是渲染dom
时使用数据产生的依赖。Dep
类,既然watcher
理解为每个数据需要监听的依赖,那么对这些依赖的收集和通知则需要另一个类来管理,这个类便是Dep
,Dep
需要做的只有两件事,收集依赖和派发更新依赖。
这是响应式系统构建的三个基本核心概念,也是这一节的基础,如果还没有印象,请先回顾上一节对极简风响应式系统的构建。
7.9 data
7.9.1 问题思考
在开始分析data
之前,我们先抛出几个问题让读者思考,而答案都包含在接下来内容分析中。
-
前面已经知道,
Dep
是作为管理依赖的容器,那么这个容器在什么时候产生?也就是实例化Dep
发生在什么时候? -
Dep
收集了什么类型的依赖?即watcher
作为依赖的分类有哪些,分别是什么场景,以及区别在哪里? -
Observer
这个类具体对getter,setter
方法做了哪些事情? -
手写的
watcher
和页面数据渲染监听的watch
如果同时监听到数据的变化,优先级怎么排? -
有了依赖的收集是不是还有依赖的解除,依赖解除的意义在哪里?
带着这几个问题,我们开始对data
的响应式细节展开分析。
7.9.2 依赖收集
data
在初始化阶段会实例化一个Observer
类,这个类的定义如下(忽略数组类型的data
):
// initData
function initData(data) {
···
observe(data, true)
}
// observe
function observe(value, asRootData) {
···
ob = new Observer(value);
return ob
}
// 观察者类,对象只要设置成拥有观察属性,则对象下的所有属性都会重写getter和setter方法,而getter,setting方法会进行依赖的收集和派发更新
var Observer = function Observer (value) {
···
// 将__ob__属性设置成不可枚举属性。外部无法通过遍历获取。
def(value, '__ob__', this);
// 数组处理
if (Array.isArray(value)) {
···
} else {
// 对象处理
this.walk(value);
}
};
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable, // 是否可枚举
writable: true,
configurable: true
});
}
Observer
会为data
添加一个__ob__
属性, __ob__
属性是作为响应式对象的标志,同时def
方法确保了该属性是不可枚举属性,即外界无法通过遍历获取该属性值。除了标志响应式对象外,Observer
类还调用了原型上的walk
方法,遍历对象上每个属性进行getter,setter
的改写。
Observer.prototype.walk = function walk (obj) {
// 获取对象所有属性,遍历调用defineReactive###1进行改写
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive###1(obj, keys[i]);
}
};
defineReactive###1
是响应式构建的核心,它会先实例化一个Dep
类,即为每个数据都创建一个依赖的管理,之后利用Object.defineProperty
重写getter,setter
方法。这里我们只分析依赖收集的代码。
function defineReactive###1 (obj,key,val,customSetter,shallow) {
// 每个数据实例化一个Dep类,创建一个依赖的管理
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
// 属性必须满足可配置
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
// 这一部分的逻辑是针对深层次的对象,如果对象的属性是一个对象,则会递归调用实例化Observe类,让其属性值也转换为响应式对象
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,s
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
// 为当前watcher添加dep数据
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
}
});
}
主要看getter
的逻辑,我们知道当data
中属性值被访问时,会被getter
函数拦截,根据我们旧有的知识体系可以知道,实例挂载前会创建一个渲染watcher
。
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}