Scheduler任务刷新机制
前言
我们在前面的 Reactivity响应式数据构建之路 与 Vue.js的运行机制与生命周期 章节中均提到了 Vue.js 的刷新机制Scheduler,
虽然在源码中Scheduler只有不过几百行代码,但它却控制着整个 Vue.js 应用的运转核心,接下来让我们来详细谈谈 Scheduler的作用。
注意Scheduler仅仅是负责控制task何时更新,它并不执行任何实际的操作。
什么是Scheduler?
如果将整个 Vue.js 应用比喻为一座工厂,那么Scheduler则是这座工厂的调度室,它负责整个 Vue.js 应用的任务队列的管理与运行,
同时也需要知道,Scheduler并不负责任何具体的任务实现,它仅仅是负责控制任务task何时执行以及其执行顺序。
Scheduler的三种类型
Vue 拥有三种不同的Scheduler任务队列类型,它们分别是:
preFlushCbs 前置任务队列
flushCbs 同步任务队列
postFlushCbs 后置任务队列
每当一个新的轮询刷新任务开启,Vue 首先会去 preFlushCbs 前置任务队列,然后去执行flushCbs 同步任务队列,最后再去执行postFlushCbs 后置任务队列。
对于每一个任务队列而言,它都存在两种状态waiting与flushing即等待执行与执行中。每当一个新的SchedulerJob开始,Vue 将会创建一个新的micro task微任务来执行这个SchedulerJob。
顾名思义,preFlushCbs会在SchedulerJob也就是组件刷新之前执行,而flushCbs则是负责执行组件刷新,最后postFlushCbs会在组件刷新完成之后执行。
Vue 在watchEffect api中提供了三种不同的函数形式:watchEffect、watchPostEffect、watchSyncEffect也正是在此基础之上构建而来的。
那么这里我们首先提出一个问题为什么 Vue 需要三种不同状态的Scheduler,而不是一种呢?我们先卖个关子,将答案放在后面揭晓。
如何开启一个新的任务队列?
任务堆栈
Vue 通过queueJob开启一个新的SchedulerJob,这里首先会对任务进行去重校验,防止重复执行,校验完成之后将新的任务推入当前任务栈中,并开始执行。
开始执行
这里首先isFlushPending将会被设置为true,意味着任务即将开始执行。之后flushJobs将会被推入下一个微任务队列中,开始正式执行任务队列。
为什么要使用resolvedPromise?
在这里有一个非常重要的细节,Vue 使用了Promise来开启一个新的SchedulerJob任务队列执行,而并不是直接开始执行,为了了解这个原因,我们首先介绍一些别的东西:“js的事件轮询机制”
js的事件轮询机制
对于浏览器而言js的运行是单线程的,script、html render、event这类的任务它们属于其中的macro task宏任务,而promise这样的则是属于micro task微任务。
微任务的执行顺序优先于宏任务,每一个宏任务之后都会紧跟一个微任务的执行,微任务的存在确保了浏览器的状态在其执行前后的一致性,因为微任务的执行会优先于HTML Render这样的宏任务。
而 Vue 正是巧妙的使用了这一规则,将每一个新的SchedulerJob通过Promise来推入下一个微任务队列,这样便可以使得其在组件刷新之前执行任务队列,同时也确保了SchedulerJob 任务队列之间的执行顺序,避免了不同任务队列之间执行顺序错乱的问题。
开始执行flushJobs
flushJobs函数将会正式开始执行 Vue 的任务队列,正如前面所提到的,这里preFlushCbs、flushCbs、postFlushCbs将会依序执行,
值得注意的是,Vue 在这里将会将任务进行重新排序,以确保父级组件的执行顺序优先于子级组件,因为父级组件首先被构造,所以其拥有更高的优先级。
执行pre前置任务
pre前置任务执行于组件刷新之前,它确保了被执行的函数的组件状态的一致性,前面我们所提到过的 watch api 正是属于前置任务的一种,
前置任务会被遍历递归执行,以确保所有的任务均被执行完毕。
执行async同步任务
遍历执行任务,在这里便会去执行组件的刷新component.update()任务,同时 Vue 会使用try catch来包裹整个流程以确保后序的后置任务得以执行。
执行post后置任务
post后置任务主要是主要是处理一些effect效果,在这里已经完成了组件的刷新component.update(),同时watch api如果选择后置模式的话,也会在这里得以执行。
nextTick函数的本质
前面我们介绍了 Vue 的SchedulerJob任务队列与微任务、宏任务之间的关系,由于 Vue 的组件刷新会触发HTML render(HTML 重绘),
而这个操作是一个宏任务,我们知道微任务的执行将会紧跟在宏任务之后,因此对于nextTick而言其本质仅仅只是创建一个的promise.then(),
将回调函数推入下一个微任务队列中,便可以保证此时函数的执行会在组件刷新完成之后。
总结
本章作为前一章 Vue.js的运行机制与生命周期 的补充文章,详细的介绍了Scheduler模块是如何去运行的。
理解Scheduler模块的核心在于理解为什么Scheduler具有pre、sync、post三种模式以及Scheduler与微任务、宏任务之间的关系。
Vue 将每一个instance.update()组件刷新推入下一个micro task微任务,由于浏览器每执行一个宏任务之后都会立刻执行下一个微任务。
Vue 巧妙的利用了这一规则以使得的组件刷新与任务执行堆栈之间不会冲突。
文献参考
Last updated

