nextTick实现
nextTick基于异步任务队列与微任务实现,将回调延迟到下次DOM更新循环结束后执行。
核心机制
JavaScript
const callbacks = []
let pending = false
function flushCallbacks() {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
使用副本执行防止执行期间新回调加入导致无限循环。
微任务降级策略
JavaScript
let timerFunc
// 优先使用Promise
if (typeof Promise !== 'undefined') {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
}
// 降级到MutationObserver
else if (typeof MutationObserver !== 'undefined') {
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)
}
}
// 最终降级setTimeout
else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
优先微任务(Promise),降级到MutationObserver,最终降级到setTimeout。
nextTick API
JavaScript
export function nextTick(cb, ctx) {
let _resolve
callbacks.push(() => {
if (cb) {
cb.call(ctx)
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
支持回调和Promise两种调用方式,同一tick内多次调用只触发一次异步任务。
与响应式系统配合
JavaScript
// Vue更新流程
function queueWatcher(watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
queue.push(watcher)
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue)
}
}
}
watcher更新队列通过nextTick批量执行,避免重复渲染。
异步更新队列
JavaScript
// 数据变化
this.count++ // 触发setter
this.count++ // 再次触发setter
this.count++ // 再次触发setter
// DOM不会立即更新
console.log(this.$el.textContent) // 还是旧值
nextTick(() => {
console.log(this.$el.textContent) // 更新后的值
})
同一tick内多次数据变更,只在nextTick统一更新DOM。
执行时机
JavaScript
// 同步代码执行
this.msg = 'new'
// 微任务队列: flushCallbacks
// -> 执行所有nextTick回调
// -> flushSchedulerQueue (DOM更新)
// 宏任务队列: (如果使用setTimeout降级)
// -> 延迟执行
微任务优先级高于宏任务,Promise.then在DOM更新前执行。
要点总结
- nextTick基于微任务(Promise)实现,降级到MutationObserver/setTimeout
- 使用
callbacks队列收集回调,pending标记防止重复触发 - 执行时使用副本防止执行期间新回调加入导致无限循环
- 与watcher队列配合实现批量DOM更新
- 支持回调函数和Promise两种调用方式
- 同一tick内多次数据变更只触发一次DOM更新
📝 发现内容有误?点击此处直接编辑