# 02-异步更新队列NextTick源码分析
# Vue为何采用异步渲染
Vue在更新DOM时是异步执行的,只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更,如果同一个watcher被多次触发,只会被推入到队列中一次,这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的,然后,在下一个的事件循环tick中,Vue刷新队列并执行实际(已去重的)工作,Vue在内部对异步队列尝试使用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout(fn, 0)代替。
# 异步渲染队列
DETAILS
// 简易实现
(function () {
// let f = n => n * 100 + 200;
let active;
let watch = function (cb) {
active = cb;
active();
active = null;
}
// 需要有个队列来存储各项任务
let queue = [];
// 通过微任务方式去执行队列中的任务
let nextTick = cb => Promise.resolve().then(cb);
// 将任务添加到队列
let queueJob = job => {
if (!queue.includes(job)) {
queue.push(job)
nextTick(flushJobs)
}
}
// 执行队列中的任务
let flushJobs = () => {
let job;
while ((job = queue.shift()) !== undefined) {
job()
}
}
// 收集更多依赖
class Dep {
// 依赖收集,将响应依赖添加到deps中
constructor() {
this.deps = new Set();
}
depend() {
if (active) {
this.deps.add(active)
}
}
// 通知依赖更新
notify() {
// 将任务加到队列中
this.deps.forEach(dep => queueJob(dep))
}
}
let ref = initValue => {
let value = initValue;
let dep = new Dep();
return Object.defineProperty({}, "value", {
get() {
dep.depend();
return value;
},
set(newValue) {
value = newValue;
dep.notify()
}
})
}
let x = ref(1);
let y = ref(1);
let z = ref(1);
watch(() => {
console.log(`x: ${x.value}, y: ${y.value}, z: ${z.value}`);
});
x.value = 2;
y.value = 3;
z.value = 4;
})()
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
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
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
# nextTick
官网-Vue.nextTick (opens new window)
官网-vm.$nextTick (opens new window)