Vue是怎样进行依赖收集的
# Vue中的依赖收集是什么
Vue的核心是响应式系统,即我们只需要关注数据的流转与变化,由数据所支撑的视图我们不用关心,视图会根据数据的变化自动更新。而构建响应式系统的基石就是依赖收集,可以说,没有依赖收集这一步,也就没有所谓的响应式系统。
简单的来说,依赖收集所作的事情就是:把一个数据被用在了什么地方、以及这个数据改变后要触发哪个更新 的信息收集起来。
这个过程会发生在三个位置:
计算属性: 触发computed watcher,收集计算属性的依赖
监听属性: 触发user watcher
render(): 触发 render watcher 依赖收集,也就是渲染模板的时候
# 三个核心类
- Observer类
通过 Object.defineProperty 方法劫持数据,getter时进行依赖收集,setter时进行通知依赖更新,这里就是Vue收集依赖的入口。
将Observer类的实例挂在__ob__属性上,提供后期数据观察时使用,实例化Dep类实例,并且将对象/数组作为value属性保存下来 - 如果value是个对象,就执行walk()过程,遍历对象把每一项数据都变为可观测数据(调用defineReactive方法处理) - 如果value是个数组,就执行observeArray()过程,递归地对数组元素调用observe()。
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor(value: any) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
// 给所有将要变成响应式的值加一个__ob__属性,也就是响应式的标志
def(value, "__ob__", this);
// 判断是否是数组,是的话调用数组的处理方法
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
}
walk(obj: Object) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
// 遍历这个对象,将其变成响应式
defineReactive(obj, keys[i]);
}
}
/**
* Observe a list of Array items.
*/
observeArray(items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
}
}
/**
* 将对象中的某个 key 变成响应式的方法,Object.defineProperty 重写 getter setter
*/
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 创建一个依赖收集器
const dep = new Dep();
// 获取对象中目标key的属性修饰符,如果 configurable 为false说明改属性不可更改,因此无法响应式,直接退出
const property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return;
}
// 获取该属性本身已有的getter和setter方法
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
// shallow 代表是否是浅层检测,如果不是浅层那么需要 observe(val)
let childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 利用该属性原有的 getter 获取值
const value = getter ? getter.call(obj) : val;
// 以下就是依赖收集的关键
// 意为:当获取这个对象的这个属性值的时候,回去判断是否存在 Dep.target,存在的话则进行依赖收集
// 第一次获取的时候是 Vue第一次向页面上挂载元素的时候,这个时候,当获取了这个属性时,会触发一次new Watcher
// 这次new Watcher 就是一个依赖,代表页面上某个地方的显示依赖了这个属性值
// new Watcher 时,就会执行 Dep.target = this 这个操作,因此会触发下面的依赖收集
if (Dep.target) {
// 依赖收集的动作,具体前往Dep的构造函数中查看
dep.depend();
// 这里判断也是深层检测
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value;
},
set: function reactiveSetter(newVal) {
// 也是用该属性原有的getter获取值
const value = getter ? getter.call(obj) : val;
// 判断新赋的值与旧值是否相同,相同就退出
if (newVal === value || (newVal !== newVal && value !== value)) {
return;
}
if (process.env.NODE_ENV !== "production" && customSetter) {
customSetter();
}
if (getter && !setter) return;
// 如果该属性原有setter,那么就调用setter,否则直接赋值
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
// 如果赋的值需要深层检测,那么也将其变成响应式
childOb = !shallow && observe(newVal);
// Dep调用notify方法,通知所有依赖
dep.notify();
},
});
}
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
- Dep类(订阅者)
Dep类的角色是一个订阅者,它主要作用是用来存放Watcher观察者对象,每一个数据都有一个Dep类实例,在一个项目中会有多个观察者,但由于JavaScript是单线程的,所以在同一时刻,只能有一个观察者在执行,此刻正在执行的那个观察者所对应的Watcher实例就会赋值给Dep.target这个变量,从而只要访问Dep.target就能知道当前的观察者是谁。
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor() {
// 每个依赖收集器的唯一标识
this.id = uid++;
// 盛放依赖的容器
this.subs = [];
}
// 添加订阅者,也就是添加一个依赖
addSub(sub: Watcher) {
this.subs.push(sub);
}
// 移除订阅者
removeSub(sub: Watcher) {
remove(this.subs, sub);
}
// 这里对应 initData方法中 ,Object.defineProperty 中 get 方法的 dep.depend()
// 意思是如果存在Dep.target 则调用Dep.target的addDep方法并且将自己作为参数传入
// 首先: Dep.target 其实就是 Watcher 的实例,其实也就是那个依赖
// 而之所以 添加依赖的动作由依赖间接完成,是因为 每个Dep 可能添加多个依赖,同样,每个依赖也可能被多个Dep添加
// 因此Watcher需要保存自己被哪些Dep添加过,判断方式就是Dep的id,作用是避免重复添加
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
// 通知所有订阅者执行update方法
notify() {
const subs = this.subs.slice();
if (process.env.NODE_ENV !== "production" && !config.async) {
subs.sort((a, b) => a.id - b.id);
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}
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
42
43
44
Dep实际上就是对Watcher的管理,Dep脱离Watcher单独存在是没有意义的。
Dep是一个发布者,可以订阅多个观察者,依赖收集之后Dep中会有一个subs存放一个或多个观察者,在数据变更的时候通知所有的watcher。
Dep和Observer的关系就是Observer监听整个data,遍历data的每个属性给每个属性绑定defineReactive方法劫持getter和setter, 在getter的时候往Dep类里塞依赖(dep.depend),在setter的时候通知所有watcher进行update(dep.notify)
- Watcher类(观察者)
Watcher类的角色是观察者,它关心的是数据,在数据变更之后获得通知,通过回调函数进行更新。
由上面的Dep可知,Watcher需要实现以下两个功能:
dep.depend()的时候往subs里面添加自己
dep.notify()的时候调用watcher.update(),进行更新视图
同时要注意的是,watcher有三种:render watcher、 computed watcher、user watcher(就是vue方法中的那个watch)。
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
// 将所有的watcher添加到实例的_watchers数组中
vm._watchers.push(this);
//其它选项,深度监听deep
if (options) {
this.deep = !!options.deep;
this.user = !!options.user;
this.lazy = !!options.lazy;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
// 回调函数,一般是用户传入的handler方法
this.cb = cb;
// watcher唯一标识
this.id = ++uid;
// 活跃状态
this.active = true;
//如果dirty为true,则重新计算,反之不进行计算,懒惰模式?
this.dirty = this.lazy;
// 用于存放自己订阅了哪个依赖收集器,Dep
this.deps = [];
this.newDeps = [];
this.depIds = new Set();
this.newDepIds = new Set();
this.expression =
process.env.NODE_ENV !== "production" ? expOrFn.toString() : "";
if (typeof expOrFn === "function") {
// 计算值的方法
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
process.env.NODE_ENV !== "production" &&
warn(
`Failed watching path: "${expOrFn}" ` +
"Watcher only accepts simple dot-delimited paths. " +
"For full control, use a function instead.",
vm
);
}
}
// 执行get方法时,会执行pushTarget(this)
// 这一步就使得 Dep.target = this,也就是往全局的依赖栈里面推入了当前的自己(watcher)
this.value = this.lazy ? undefined : this.get();
}
/**
* 计算值,并重新收集依赖项
*/
get() {
// 将自己推入全局那个订阅者栈 targetStack,因为当前自己正在执行
pushTarget(this);
let value;
const vm = this.vm;
try {
// 调用getter计算值
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 方法查看
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value;
}
/**
* 添加依赖的动作,对应Dep构造函数中的depend
*/
addDep(dep: 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);
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps() {
let i = this.deps.length;
while (i--) {
const dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}
let tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
}
/**
* 依赖项变化,触发update方法
*/
update() {
if (this.lazy) {
// 这个lazy也对应了computedwatcher ,如果依赖更新,那么更新dirty为true,以触发computed的重新计算
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
}
/**
* 实际调用用户传入的cb回调函数的方法
*/
run() {
if (this.active) {
const value = this.get();
// 这里可以看出先是 执行this.get 获取到最新的值 作为新值value
// 然后获取到保存的 this.value 作为oldValue
// 最后触发 this.cb.call(this.vm, value, oldValue)
// 因此这也是对应了我们使用watch时,handler方法里可以接收两个参数,第一个是新值,第二个是旧值
if (value !== this.value || isObject(value) || this.deep) {
const oldValue = this.value;
this.value = value;
if (this.user) {
const info = `callback for watcher "${this.expression}"`;
invokeWithErrorHandling(
this.cb,
this.vm,
[value, oldValue],
this.vm,
info
);
} else {
this.cb.call(this.vm, value, oldValue);
}
}
}
}
/**
* 计算值
*/
evaluate() {
// 对应createComputedGetter里面的evaluate方法
// 先计算值
// 后更新dirty为false
this.value = this.get();
this.dirty = false;
}
/**
* 意思是假如a属性依赖于b属性,那么会将b属性的所有依赖添加到a身上
*/
// 遍历这个watcher里面的所有dep 也就是自己订阅了的那个Dep
// 然后执行Dep的depend方法 里面做了这件事 ( Dep.target.addDep(this) )
depend() {
let i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown() {
if (this.active) {
// 判断如果实例vm还没有开始卸载,那就移除_watchers
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this);
}
//遍历移除所有依赖项
let i = this.deps.length;
while (i--) {
this.deps[i].removeSub(this);
}
this.active = false;
}
}
}
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# 简易版vue,说明依赖收集过程,实现响应式效果
// 数据响应式
/**
* 在key发生变化时可以感知做出操作
* @param {*} obj 对象
* @param {*} key 需要拦截的key
* @param {*} val 初始值
*/
function defineReactive(obj, key, val) {
// 递归
observer(val);
// 创建Dep实例
// data中的数据每一项都会进入到此,创建一个Dep
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
// 依赖收集
// 只有在调用时存在target才会被Dep收集更新(在初始化时设置静态属性target为watcher,被收集)
Dep.target && dep.addDep(Dep.target);
return val;
},
set(newVal) {
if (newVal !== val) {
observer(newVal);
val = newVal;
// 被修改时通知所有属于自己的watcher更新
// 一个watcher对应一处依赖,一个Dep对应一个data中的数据 一个dep的更新可以指向多个watcher
dep.notify();
}
}
});
}
// 遍历obj做响应式
function observer(obj) {
if (typeof obj !== "object" || obj === null) {
return;
}
// 遍历obj的所有key做响应式
new Observer(obj);
}
// 遍历obj的所有key做响应式
class Observer {
constructor(value) {
this.value = value;
if (Array.isArray(this.value)) {
// TODO
} else {
this.walk(value);
}
}
// 对象响应式
walk(obj) {
Object.keys(obj).forEach((key) => {
defineReactive(obj, key, obj[key]);
});
}
}
function proxy(vm) {
Object.keys(vm.$data).forEach((key) => {
Object.defineProperty(vm, key, {
get: () => vm.$data[key],
set: (v) => (vm.$data[key] = v)
});
});
}
class Vue {
constructor(options) {
this.$options = options;
this.$data = options.data;
this.$methods = options.methods;
observer(this.$data);
proxy(this);
new Compile(options.el, this);
}
}
// 解析模板
// 1.处理插值
// 2.处理指令和事件
// 3.以上两者的初始化和更新
class Compile {
constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el);
if (this.$el) {
this.compile(this.$el);
}
}
compile(el) {
// 遍历el的子节点 判断他们的类型做相应的处理
const childNodes = el.childNodes;
if (!childNodes) return;
childNodes.forEach((node) => {
if (node.nodeType === 1) {
// todo
// 元素 处理指令和事件
const attrs = node.attributes;
Array.from(attrs).forEach((attr) => {
// c-xxx="abc"
const attrName = attr.name;
const exp = attr.value;
if (attrName.startsWith("v-")) {
const dir = attrName.substring(2);
this[dir] && this[dir](node, exp);
} else if (attrName.startsWith("@")) {
const dir = attrName.substring(1);
this.eventFun(node, exp, dir);
}
});
} else if (this.isInter(node)) {
// 文本
this.compileText(node);
}
// 递归
if (node.childNodes) {
this.compile(node);
}
});
}
// 事件处理函数
eventFun(node, exp, dir) {
node.addEventListener(dir, this.$vm.$methods[exp].bind(this.$vm));
}
update(node, exp, dir) {
// 初始化
const fn = this[dir + "Update"];
fn && fn(node, this.$vm[exp]);
// 更新
new Watcher(this.$vm, exp, function (val) {
fn && fn(node, val);
});
}
text(node, exp) {
this.update(node, exp, "text");
}
textUpdate(node, val) {
node.textContent = val;
}
modelUpdate(node, val) {
node.value = val;
}
html(node, exp) {
this.update(node, exp, "html");
}
htmlUpdate(node, val) {
node.innerHTML = val;
}
// todo
// 编译文本
compileText(node) {
this.update(node, RegExp.$1, "text");
}
// 双向绑定处理
model(node, exp) {
// console.log(node, exp);
this.update(node, exp, "model");
node.addEventListener("input", (e) => {
// console.log(e.target.value);
// console.log(this);
this.$vm[exp] = e.target.value;
});
}
// 是否插值表达式
isInter(node) {
// console.log('node', node);
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
}
// 监听器:负责依赖更新
// const watchers = [];
class Watcher {
constructor(vm, key, updateFn) {
this.vm = vm;
this.key = key;
this.updateFn = updateFn;
// 触发依赖收集
Dep.target = this;
this.vm[this.key];
Dep.target = null;
}
// 未来被Dep调用
update() {
// 执行实际的更新操作
this.updateFn.call(this.vm, this.vm[this.key]);
}
}
class Dep {
constructor() {
this.deps = [];
}
addDep(dep) {
this.deps.push(dep);
}
notify() {
this.deps.forEach((dep) => dep.update());
}
}
new Vue({
el: "#app",
data: {
counter: 1,
number: 10,
desc: `<h1 style="color:red">hello CVue</h1>`
},
methods: {
add() {
console.log("add", this);
this.counter++;
},
changeInput() {
console.log("changeInput");
}
}
});
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# 参考文章链接
带你快速手写一个简易版vue了解vue响应式 (opens new window)
【Vue源码学习】依赖收集 (opens new window)