知识脉络
- JS是单线程的,同步任务在主线程上执行,而异步任务由eventloop机制管理,异步任务分两类,microtask和macrotask,具体机制是执行一个macrotask后,清空所有的microtask
- nextTick正是利用macrotask和microtask的机制,主线程进行所有组件的属性修改后,在microTask或microTask当中批量高效地进行更新。
Q:现在知道nextTick有一个队列保存着回调,问题是这些回调都是从哪里添加的?
A:Vue内部真正调用nextTick的只有一处:scheduler,这个scheduler保存着一个watcher的队列,通过queueWatcher函数来添加watcher,通过flushSchedulerQueue来清空watcher队列。
queueWatcher
- 如果存在重复watcher,则直接跳过
- 然后会判断是不是正在清空队列
- 如果不是则直接添加watcher
- 如果是,则通过
queue.splice(index+1, 0, watcher)插入到队列当中,- 其中index是正在执行的watcher的下标或者未被执行的且watcherid小于当前watcher的下标
最终判断是不是正在等待被执行,也就是判断flushSchedulerQueue有没有被nextTick添加进任务队列,如果没有则添加进去然后将waiting置为true
if (!flushing) {queue.push(watcher)} else {// if already flushing, splice the watcher based on its id// if already past its id, it will be run next immediately.let i = queue.length - 1while (i > index && queue[i].id > watcher.id) {i--}queue.splice(i + 1, 0, watcher)}
flushSchedulerQueue
主要干了两件事
对queue进行升序排序,保证了一下几点
- 父节点比子节点先更新,因为父节点可能有属性需要传给子节点
- 用户的watcher比渲染watcher先执行,因为用户可能在watcher中再次修改组件属性
- 子组件被父组件卸载,其相关watcher可以不被执行
- 调用activated和updated钩子
梳理
以以下代码为例:
当我们执行onClick时,由于this.count发生变化,所以会先将组件渲染的watcher通过queueWatcher函数添加到shceduler的queue当中,然后queueWatcher会执行new Vue({template: '<div @click="onClick">{{count}}</div>',data() {return {count: 1}},methods: {onClick() {this.count += 1Vue.nextTick(() => {console.log(this.count)})}}}).$mount('#app')
nextTick(flushSchedulerQueue),添加清理队列函数进nextTick队列中。Vue.nextTick也会将传入的函数添加到nextTick队列当中。当主线程清空,nextTick的队列开始清空,首先会执行的自然是flushSchedulerQueue,然后就是用户传入的回调。
