前言
为什么要写一篇关于 nextTick 的源码分析呢?
主要是因为 nextTick 与 JS 的事件循环(Event Loop)有关,这块知识还是比较重要的,尤其当我们的程序复杂了,很可能就会遇到因为 JS 顺序导致的问题。
本文基于 2.6.11 版本的代码进行分析。
事件循环
JS 执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
其中异步任务又分为宏任务(macro task)和微任务(micro task),在浏览器环境:
- 宏任务
- script 标签中的代码
- setTimeout
- setInterval
- setImmediate
- MessageChannel
- I/O
- UI 交互事件
- requestAnimationFrame
- 微任务
- Promise.then
- MutationObsever
Node.js 和这个有些不同,这里不展开说明了,感兴趣可以参考这里。
- 程序开始执行时,执行栈中没有任务。
- 微任务队列也没有任务,我们平时写的 script 中的代码算作宏任务,所以主线程就会从宏任务队列中取出
一个任务
放到执行栈中进行执行。 - 在执行栈中的代码执行完后,就会从微任务队列中取出
所有任务
放到执行栈中进行执行。
至此,一次事件循环就结束了,接下来继续循环上面的步骤。循环逻辑用代码表示如下:
for (macroTask of macroTaskQueue) {
// 1. Handle current MACRO-TASK
handleMacroTask();
// 2. Handle all MICRO-TASK
for (microTask of microTaskQueue) {
handleMicroTask(microTask);
}
}
nextTick 语法
我们先来看下 nextTick 的语法与介绍。
vm.$nextTick( [callback] )
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
这里注意,Vue 实例
和构造函数
上都有 nextTick 方法。
什么情况下,我们会用到 nextTick 方法呢?
理想情况,我们改变了 Vue 中的数据状态,对应的 DOM 视图应该刷新。但是,Vue 中并没有这样做,它把 watch 的更新放到了队列当中,对应的代码细节可以查看 Vue 源码之 computed 和 watch 这篇博文。
export default class Watcher {
update () {
if (this.lazy) {
this<