现象
?对于nextTick相信大家都并不陌生,那么nextTick的本质是对JS事件循环的一种应用。那么相信大家都遇到过这种情况,比如说我们对DOM1进行操作并改变DOM1中的数据,下面使用一个变量去获取DOM1中的数据,发现获取到的数据是DOM1变化之前的数据,这就是我们会产生疑惑的地方。
为什么?
?这是因为Vue对Dom的更新是异步的,异步是代表着当被处理数据是动态变化时,此时对应的Dom未能同步更新就会导致数据已经更新(Model层的数据已经更新),但是视图层并没有更新(Dom未进行更新)。此时我们可以通过使用nextTick的回调函数中获取到更新后的Dom值。
源码解析
nextTick
const callbacks = []
let pending = false
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)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
?分析这段代码,我们可以看到在nextTick的外层定义变量就形成了一个闭包callbacks,当每次调用nextTick时实际是在向callbacks这个队列中去添加对应的回调函数进行等待调用。当pending第一次为false的时候,将pending设置为true,然后执行timeFunc函数(稍后我们解读一下timeFunc函数),那么下一次执行nextTick时,就只会把对应的回调函数加入到当前的任务队列中,不会再次执行timerFunc函数。nextTick后续会返回一个Promise实例,并将resolve赋值给_resolve。
timerFunc
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
?这段代码主要是调用flushCallbacks方法,把其放在相对应的微任务或者宏任务去执行调用。主要通过timeFunc来去调用flushCallbacks方法,依次判断是否支持promise.then->MutationObserver->setImmediate->setTimeout 进行优先创建微任务,其次兼容宏任务,无论哪种哪种方式创建的timeFunc最后都会执行flushCallbacks函数。
flushCallbacks
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
?对于flushCallbacks负责执行callbacks里的回调,把callbacks进行复制一份,然后将callbacks进行置空,将所拷贝出来数组中的每个函数依次执行一遍。所以flushCallbacks仅仅是用来执行callbacks中的回调函数。
总结
- 当调用nextTick时把nextTick中对应的回调函数放入到callbacks中进行等待执行,并且返回Promise实例,当第一次调用时则会开启pending锁,并执行timerFunc函数将执行事件队列的函数放到微任务/宏任务中,去模拟Vue中自己的异步事件队列。
- timerFunc通过一次判断微任务与宏任务,依次对
Promise->MutationObserver->setImmediate->setTimeout ,来去将执行函数放在微任务/宏任务中 - 当事件循环到了微任务/宏任务时,调用flushCallbacks,依次执行对应callbacks中的回调。
?对nextTick的源码分析到此结束啦,希望各位小伙伴能够有所收获~
|