概念:
nextTick:
nextTick主要是使用了宏任务 (macrotask) 和微任务 (microtask) ,定义了一个异步方法,多次调用 nextTick 会将方法存入callback队列中,通过这个异步方法清空当前队列
macrotask:
setTimeout, setInterval, setImmediate, I/O, UI rendering
microtask:
process.nextTick, Promise, MutationObserver
顺序:
任务队列中,在每一次事件循环中,macrotask只会提取一个执行,而microtask会一直提取,直到microsoft队列为空为止,主线程执行完成该任务后又会检查microtasks队列并完成里面的所有任务后再执行macrotask
场景:
- 在数据变化后要执行的某个操作,并随着操作需要改变dom更新的时候
- 在 created 或者 mounted 阶段,需要操作后更新dom时
- 如果先执行了nextTick再更新数据会无效
nextTick和$nextTick:
nextTick(callback): 全局方法,当数据发生变化,更新后执行回调
$nextTick(callback): 实例方法,自动把context参数绑定为调用它的实例,当dom发生变化,更新后执行的回调,一般会使用this. $nextTick
原理:
- 调用nextTick并传入两个参数:回调函数cb和回调函数的执行上下文ctx
- 判断是否有回调函数
- 有就存入队列
- 没有就返回promise
- 判断是否在执行回调函数
- 如果没有则执行timeFunc异步方法,多次执行nextTick只会执行一次timerFunc
- timeFunc中选择一个异步方法:
(1)先尝试promise回调,进行异步执行flushCallbacks
(2)若不支持则继续尝试MutationObserver回调,会创建一个文本节点进行监听
(3)若不支持则继续尝试setImmediate回调,在setImmediate下执行flushCallbacks
(4)都不支持则使用setTimeout(flushCallbacks, 0) - 执行flushCallbacks方法
源码:
nextTick
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 将拿到的回调函数存放到数组中
callbacks.push(() => {
if (cb) {
try { // 错误捕获
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// 如果当前没有在执行,就会执行timeFunc
if (!pending) {
//标记正在执行
pending = true
// 多次执行nextTick只会执行一次,timerFunc就是一个异步方法
timerFunc()
}
}
timeFunc
// 判断是否原生支持promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
// flushCallbacks就包裹了一个promise
timerFunc = () => {
// 如果支持则异步的去执行flushCallbacks
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
// 标记微任务
isUsingMicroTask = true
// 判断是否原生支持MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 也是一个微任务
let counter = 1
// new了一个MutationObserver类
const observer = new MutationObserver(flushCallbacks)
// 创建了一个文本节点
const textNode = document.createTextNode(String(counter))
// 原生api,帮我们监听一个节点
// 当数据发生变化了就会异步执行flushCallbacks方法
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
// 数据更新
textNode.data = String(counter)
}
// 标记微任务
isUsingMicroTask = true
// 判断是否原生支持setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// setImmediate原生方法,默认ie下有,高版本的谷歌也支持
timerFunc = () => {
// 直接执行
setImmediate(flushCallbacks)
}
} else {
// 如果以上都不支持则采用setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
flushCallbacks
// 回调函数队列
const callbacks = []
// 空闲状态准备执行
let pending = false
// 多个nextTick中传递的回调函数依次执行
function flushCallbacks () {
pending = false
// 拷贝一份禁止套娃
const copies = callbacks.slice(0)
// 清空队列
callbacks.length = 0
// cb执行过程中可能又会往callbacks中加入内容
// 遍历完拷贝的队列,新任务在下一轮存入callbacks执行
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}