依赖收集
源码地址:传送门
Vue
为用户提供了一个特别方便的功能:数据更新时自动更新DOM
。本文将详细介绍Vue
源码中该特性实现的核心思路,深入理解Vue
数据和视图的更新关系。
思路梳理
如何追踪变化
这是Vue
官方数据变化引发视图更新的图解:
![30bc626383d74ae89e3768b0dc9511e5.png](https://i-blog.csdnimg.cn/blog_migrate/e097f96cfcbd8128492a80c860a405b6.jpeg)
用文字描述的话,其流程如下:
- 组挂载,执行
render
方法生成虚拟DOM
。此时在模板中用到的数据,会从vm
实例上进行取值 - 取值会触发
data
选项中定义属性的get
方法 get
方法会将渲染页面的watcher
作为依赖收集到dep
中- 当修改模板中用到的
data
中定义的属性时,会通知dep
中收集的watcher
执行update
方法来更新视图 - 重新利用最新的数据来执行
render
方法生成虚拟DOM
。此时不会再收集重复的渲染watcher
渲染watcher
就是用来更新视图的watcher
,具体的执行过程在组件初渲染中有详细介绍,它的主要作用如下:
1. 执行vm._render
方法生成虚拟节点
2. 执行vm._update
方法将虚拟节点处理为真实节点挂载到页面中
需要注意的是,数组并没有为每个索引添加set/get
方法,而是重写了数组的原型。所以当通过调用原型方法修改数组时,会通知watcher
来更新视图,保证页面更新。
Dep
收集watcher
并且在数据更新后通知watcher
更新DOM
的功能主要是通过Dep
来实现的,其代码如下:
let id = 0;
class Dep {
constructor () {
// dep的唯一标识
this.id = id++;
this.subs = [];
}
addSub (watcher) {
this.subs.push(watcher);
}
// 通过watcher来收集dep
depend () {
Dep.target.addDep(this);
}
// 执行所有收集watcher的update方法
notify () {
this.subs.forEach(sub => {
sub.update();
});
}
}
Dep
会将watcher
收集到内部数组subs
中,之后通过notify
方法进行统一执行。
代码中还会维护一个栈,来保存所有正在执行的watcher
,执行完毕后watcher
出栈。
const stack = [];
// 当前正在执行的watcher
Dep.target = null;
export function pushTarget (watcher) {
stack.push(watcher);
Dep.target = watcher;
}
export function popTarget () {
stack.pop();
Dep.target =