跳到主要内容

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

降级策略

优先微任务:

  1. Promise
  2. MutationObserver

降级宏任务:

  1. setImmediate
  2. 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)
}
}