nextTick
nextTick: 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
nextTick 方法主要是使用了 宏任务 和 微任务 (可以看看我的另一篇文章 Event loop),定义了一个异步方法 timerFunc。多次调用 nextTick 会将方法存入队列 callbacks 中,通过这个异步方法清空当前队列。
声明了 timerFunc,优先检测是否支持 Promise,如果不支持在检测是否支持 MutationObserver,失败的话再检查 setImmediate,如果还不支持,最后只能是 setTimeout,可见 setTimeout 的性能是最差的。
再看对外暴露的 nextTick 函数,在 Vue 响应式(链接) 中 最后执行了 nextTick(flushSchedulerQueue),把传入的回调函数 cb 压入 callbacks 数组,然后 在下⼀个 tick 执行 flushCallbacks , flushCallbacks 中 对 callbacks 遍历,然后执行相应的回调函数。
这里使用 callbacks 而不是直接在 nextTick 中执行回调函数的原因是保证在同⼀个 tick 内多次执行 nextTick ,不会开启多个异步任务,而把这些异步任务都压成⼀个同步任务,在下⼀个 tick 执行完毕。
示例
比如 this.onOfData = XX 和 this.$nextTick(()=>{}),这两个任务,首先会将 this.onOfData 的改变加入到 callbacks 队列中,此时 会执行
if (!pending) {
pending = true;
timerFunc();
}
timerFunc()执行, 假设我们支持的是 Promise,那么就会 执行 Promise.resolve().then(flushCallbacks);
但是 同步任务 还没执行完,也就是 this.$nextTick(()=>{}) 这个同步任务没执行,所以优先执行这个,加入到队列 callbacks , 此时pending 为 true , 所以就不会 走 if 判断 让 if 语句的逻辑只执行一次;
接着同步任务都执行完成,接着就 执行 微任务 flushCallbacks(), 会执行完 callbacks 中的所有任务,此时 callbacks 有两个任务,一个是 data 的改变, 一个是 $nextTick。
nextTick 函数最后还有⼀段逻辑:
if (!cb && typeof Promise !== 'undefined')
{ return new Promise(resolve => { _resolve = resolve }) }
这是当 nextTick 不传 cb 参数的时候,提供⼀个 Promise 化的调用,⽐如:
nextTick().then(() => {})
callbacks.push 中的函数就会走else 逻辑 ()=> _resolve(ctx);
当 _resolve 函数执行,也就会直接 resolve(),就会跳到 then 的逻辑中。