scheduler
Vue 的调度器(scheduler)是 Vue 响应式系统的一个内部机制,负责协调和优化异步更新。调度器的主要职责是合理安排视图更新的时机,确保在一个事件循环(event loop)中尽可能高效地批量处理组件的重新渲染,从而避免不必要的重复渲染和性能损耗。
[响应式数据变化]
↓
[收集更新任务]
↓
[等待同一事件循环中的其他变化]
↓
[异步队列执行(微任务/宏任务)]
↓
[批量更新]
↓
[视图更新]
↓
[`nextTick` 回调执行]
工作原理
1.异步更新队列
Vue 2 在内部维护了一个待处理的 watcher 队列。当响应式数据发生变化时,触发的 setter 会通知所有订阅者(watcher),而不是立即执行 watcher 的回调来更新视图,Vue 会将这些 watcher 推入一个队列中等待异步执行。
2.批量更新
Vue 2 的调度器会对等待队列中的 watcher 进行去重处理,确保同一个 watcher 在同一轮更新周期内只被添加一次。这样可以避免因为一个数据多次变更引起的多余计算和渲染。
3.使用 nextTick
Vue 提供了 nextTick 方法,允许开发者在调度器完成当前所有更新后执行回调函数。这对于需要在 DOM 更新完成后执行操作的场景非常有用。
源码实现
let queue = [];
let has = {};
let waiting = false;
function flushSchedulerQueue() {
queue.forEach((watcher) => {
watcher.run(); // 执行 watcher 更新
});
queue = [];
has = {};
waiting = false;
}
function queueWatcher(watcher) {
const id = watcher.id;
if (has[id] == null) {
has[id] = true;
queue.push(watcher);
if (!waiting) {
waiting = true;
Vue.nextTick(flushSchedulerQueue);
}
}
}
- queue 是待执行 watcher 的队列。
- has 对象用来记录队列中已有的 watcher,防止同一个 watcher 被多次加入。
- flushSchedulerQueue 是在下一个事件循环中执行的,用于遍历和执行队列中的所有 watcher。
- queueWatcher 用于将 watcher 添加到队列中,并在需要时安排异步刷新队列。
nextTick
源码地址: https://github.com/vuejs/vue/blob/main/src/core/util/next-tick.ts#L94
降级策略
优先微任务:
- Promise
- MutationObserver
降级宏任务:
- setImmediate
- setTimeout
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) ||
// PhantomJS and iOS 7.x
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 {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}