准备
vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。
回顾
如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》
依赖收集
所谓依赖收集,就是利用Vue定义的Dep
类去收集当前正在执行中的watcher
。
依赖收集的操作在data
的get
过程中,所以我们进入defineReactive
方法,去看看在get
的过程中做了什么操作。
defineReactive
const dep = new Dep();
...
//访问该值时,依赖收集
get: function reactiveGetter() {
//如果有原始的getter
//value就是调用原始的getter后的结果
//如果没有,value就直接是val
const value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value;
},
一开始先实例化了一个Dep
,之后在get
过程中有一个判断,如果存在Dep.target
就执行dep
实例的depend
方法。在这里我提前透露下,其实这个Dep.target
就是当前正在运行的watcher
。现在先进入depend
方法一起看看:
dep.depend
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
先判断当前是否有正在运行的watcher
,如果存在就调用这个watcher
的addDep
方法,进入addDep
方法。
watcher.addDep
addDep(dep: Dep) {
const id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
}
addDep
方法主要进行了两个操作:
- 将刚刚的
Dep
实例保存到实例属性this.newDeps
中 - 调用
dep.addSub
,然后传入当前watcher
的实例。进入dep.addSub
dep.addSub
addSub (sub: Watcher) {
this.subs.push(sub)
}
由这里可以看到,其实dep
将收集到的依赖(watcher
)全部放入了this.subs
这个数组中。
到这里,其实依赖收集已经讲完了,接下来该讲的应该就是派发更新了,但是我刚刚说Dep.target
就是当前正在运行的watcher
,这又是从哪里得知的呢?
Dep.target
在Dep
类的定义中可以看到,target
是一个静态属性:
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
...
}
Dep.target = null
静态属性的特点就是全局唯一,因为静态属性是随着类的加载而加载的。
那这个target
是在哪里被赋值的呢?先不急看在哪赋值,我们先看看watcher
是在什么时候实例化的,因为当我们知道watcher
是在什么时候实例化,那就知道了当前正在运行的watcher
是怎么赋值到Dep.target
身上的了。
mountComponent
我们可以在Vue挂载的阶段,mountComponent
函数中看到这么一段定义:
updateComponent = () => {
vm._update(vm._render(), hydrating);
};
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, "beforeUpdate");
}
},
},
true /* isRenderWatcher */
);
在这里我们看到了一个watcher
的实例化过程,传入了updateComponent
这个函数,updateComponent
会调用_render
,这些逻辑我们前面都已经分析过了,在这不多赘述。
直接进入watcher
的构造器:
class Watcher
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
....
this.value = this.lazy ? undefined : this.get();
}
在构造器的最后一行我们看到这里调用了一个get
方法,进入get
方法
watcher.get
get() {
pushTarget(this);
let value;
const vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`);
} else {
throw e;
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
//清除无用的watcher
this.cleanupDeps();
}
return value;
}
这里的逻辑的大概意思是先执行pushTarget
传入当前watcher
实例,之后调用this.getter
,这个this.getter
在上面mountComponent
的过程中就是updateComponent
函数,于是这个时候就会执行vm._update
然后就是__patch__
,最后更新DOM,也就是说在new Watcher
的过程中就会执行一次渲染操作。那么,那个Dep.target
是在哪里赋值的呢?回过来看看这个get
方法的第一行,不是有个pushTarget
吗?
pushTarget
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
pushTarget
做了两个操作:
- 将当前的正在运行的
watcher
实例收集到了一个栈结构targetStack
中 - 将当前的正在运行的
watcher
实例赋值给了Dep.target
targetStack
栈结构,就是将当前的watcher
保存起来,毕竟Dep.target
只能保存一个watcher
实例,如果存在组件嵌套,当前watcher
还未执行完,就被子组件的watcher
给覆盖了,所以需要存储watcher
。
defineReactive
回到defineReactive
函数,我们继续往下看:
if(childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
这里其实是比较难理解的一个点。
如果存在childOb
,就调用childOb
实例属性dep
上的depend()
方法。
childOb
其实是$data
中的对象添加响应式后的结果,我们来举个例子:
data:() => ({
a:{
b:"1"
}
})
这是一个Vue实例的data
选项,其中有一个对象a
,a
中有一个键b
,b
的值为"1"
。在这里childOb
就是指a
对象的__ob__
属性。源码中调用了childOb.dep.depend()
,意思是将a
对象整个对象的结构也加入了依赖中,为的是监听对象属性的增加和删除,而不是为了监听对象某个属性的值是否发生改变。
继续向下看,如果发现value
是一个数组,就会调用dependArray
:
function dependArray(value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i];
e && e.__ob__ && e.__ob__.dep.depend();
if (Array.isArray(e)) {
dependArray(e);
}
}
}
有了上面childOb
的理解的基础上,这个函数的理解就比较简单了。
如果数组中有对象,也需要监听数组中的对象结构是否发生改变,然后再派发更新。
总结
其实依赖收集是Vue中比较难的部分,因为这里的源码非常的绕,一会是Dep
,一会是Watcher
,一会又是defineReactive
甚至还有mountComponent
,逻辑顺序比较复杂,需要好好梳理后才能彻底理解,在这里我推荐一个老师的课程,很短,没几集,但是将Observer
、Dep
、Watcher
、observe
、defineReactive
梳理得非常清楚、浅显易懂:点击查看,重点是免费的哦。
好了,接下来我们要进入一个更加复杂的逻辑了,派发更新,准备好了吗?我们下一章见~