记得咱讲微任务的时候提到了最常用的两个创建微任务 API :Promise
和nextTick
。Promise
咱们已经讲过了,所以今天一起来看看nextTick
前言
nextTick
是 Vue 中一个特殊的 API 。一般用作 DOM 更新完毕之后执行一个回调。(vm.$nextTick([callback])
)
用法
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法Vue.nextTick
一样,不同的是回调的this
自动绑定到调用它的实例上。
1 | new Vue({ |
我们更该数据之后,list
中新加入的数据DDDDD
会立刻呈现出来。但是我们打印他们 DOM 结构的length
时候会显示3
1 | mounted: function () { |
此时使用 Vue 中的nextTick
就可以解决问题
1 | mounted: function () { |
当你设置 vm.someData = ‘new value’ ,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
从一个栗子说起
1 | new Promise((resolve) => { |
上边的代码输出结果是1、4、7、2、3、5、6
。同样是创建微任务的宏任务,凭什么你nextTick
就是比我setTimeout
先执行?
宏任务 & 微任务
一个宿主环境只有一个时间循环,但可以有多个任务队列。(macrotask & microtask
)。每次事件循环的时候,都会先执行红任务队列中的任务,再执行微任务队列中的任务。
宏任务:
script
、setTimeout
、setInterval
、setImmediate(只有IE支持)
、I/O
、UI rendering
微任务:
process.nextTick
、Promise
、Object.observer
、MutationObserver
微任务都会被添加到当前循环的微任务队列之中。所以会比当前循环中的所有宏任务要后执行,会比下个循环中的宏任务要先执行。
单拎出来process.nextTick
和Promise
1 | process.nextTick(() => { |
结果打印了1、3、2
在node
环境下,_tickCallback
在每一次执行完TaskQueue
中的一个任务后被调用,而这个_tickCallback
中实质上干了两件事
- 执行完所有
nextTickQueue
中的任务 - 执行
_runMicrotasks
函数,执行microtask
中的部分(promise.then()
注册的回调)。所以很明显优先级process.nextTick > promise.then
。
setImmediate???
1 | setImmediate(() => { |
这东西跑的结果好像不太稳定,有时候打印1,有时候打印2。
nodejs
官网给出的解释是:
setImmediate()
: 是被设计用来一旦当前阶段的任务执行完后执行。setTimeout()
: 是让代码延迟执行。
如果没有在一个I/O周期执行,那么其执行顺序是不确定的。
如果在一个I/O周期执行,setImmediate
总是优先于setTimeout
执行。
1 | const fs = require('fs'); |
如果这样的话那么就总是先打印immediate
再打印timeout
啦。
贴段源码
1 | export const nextTick = (function () { |
callbacks就是缓存的所有回调函数,nextTickHandler就是实际调用回调函数的地方。
1 | if (typeof Promise !== 'undefined' && isNative(Promise)) { |
为让这个回调函数延迟执行,vue优先用promise来实现,其次是html5的MutationObserver,然后是setTimeout。前两者属于microtask,后一个属于macrotask。下面来看最后一部分
1 | return function queueNextTick(cb?: Function, ctx?: Object) { |
这就是我们真正调用的nextTick函数,在一个event loop内它会将调用nextTick的cb回调函数都放入callbacks中,pending用于判断是否有队列正在执行回调,例如有可能在nextTick中还有一个nextTick,此时就应该属于下一个循环了。最后几行代码是promise化,可以将nextTick按照promise方式去书写(暂且用的较少)。