4.异步更新

dep.notify()

1
2
3
4
5
6
7
8
9
10
11
/**
* 通知 dep 中的所有 watcher,执行 watcher.update() 方法
*/
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
// 遍历 dep 中存储的 watcher,执行 watcher.update()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}

watcher.update()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 根据 watcher 配置项,决定接下来怎么走,一般是 queueWatcher
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
// 懒执行时走这里,比如 computed
// 将 dirty 置为 true,可以让 computedGetter 执行时重新计算 computed 回调函数的执行结果
this.dirty = true
} else if (this.sync) {
// 同步执行,在使用 vm.$watch 或者 watch 选项时可以传一个 sync 选项,
// 当为 true 时在数据更新时该 watcher 就不走异步更新队列,直接执行 this.run
// 方法进行更新
// 这个属性在官方文档中没有出现
this.run()
} else {
// 更新时一般都这里,将 watcher 放入 watcher 队列
queueWatcher(this)
}
}

面试题

Vue 的异步更新机制是如何实现的?

Vue 的异步更新机制的核心是利用了浏览器的异步任务队列来实现的,首选微任务队列,宏任务队列次之。

当响应式数据更新后,会调用 dep.notify 方法,通知 dep 中收集的 watcher 去执行 update 方法,watcher.update 将 watcher 自己放入一个 watcher 队列(全局的 queue 数组)。

然后通过 nextTick 方法将一个刷新 watcher 队列的方法(flushSchedulerQueue)放入一个全局的 callbacks 数组中。

如果此时浏览器的异步任务队列中没有一个叫 flushCallbacks 的函数,则执行 timerFunc 函数,将 flushCallbacks 函数放入异步任务队列。如果异步任务队列中已经存在 flushCallbacks 函数,等待其执行完成以后再放入下一个 flushCallbacks 函数。

flushCallbacks 函数负责执行 callbacks 数组中的所有 flushSchedulerQueue 函数。

flushSchedulerQueue 函数负责刷新 watcher 队列,即执行 queue 数组中每一个 watcher 的 run 方法,从而进入更新阶段,比如执行组件更新函数或者执行用户 watch 的回调函数。

Vue 的 nextTick API 是如何实现的?

Vue.nextTick 或者 vm.$nextTick 的原理其实很简单,就做了两件事:

  • 将传递的回调函数用 try catch 包裹然后放入 callbacks 数组
  • 执行 timerFunc 函数,在浏览器的异步任务队列放入一个刷新 callbacks 数组的函数

总结

异步更新的入口点就是 setter 中最后调用的 dep.notify() 方法。dep.notify() 会遍历 dep 中的 watcher,并执行 watcher.update()。在 update 中,如果 watcher.lazy 是 true,那么将 dirty 也置为 true,就是控制懒执行。如果 watcher.sync 为 true,就直接调用 run 函数同步更新,否则走异步更新,调用 queueWatcher,将 watcher 放入 watcher 队列,在 queueWatcher 中会根据 id 判断 watcher 是否已经入过队了,入过了直接跳过。然后再根据 flushing 判断队列是否处于刷新状态,如果没有的话就将 watcher 就直接入队,否则需要做一定处理,根据 id 保证队列中的有序性。最后再根据 waiting 判断队列中是否有刷新函数(flushSchedulerQueue),如果没有的话,使用 nextTick 将该函数放入 callbacks 数组里。如果当前没有其它 nextTick 在执行的话,就会调用 timerFunc 函数,会根据环境,做一个兼容性判断,选择某一种异步执行的方法调用 flushCallbacks 函数。优先级是 promise.then > object.observe > setImmediate > setTimeout。flushCallbacks 遍历 callbacks 执行其中每一个刷新函数,刷新函数实际就是在调用 watcher.run 从而执行组件更新函数(updateComponent)或者 watch 的回调函数。

参考

https://juejin.cn/post/6951568091893465102


4.异步更新
http://example.com/2022/10/23/4-异步更新/
Author
John Doe
Posted on
October 23, 2022
Licensed under