全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-20 10 分钟 ✍️ juanwangdev

异步更新队列与nextTick

Vue通过异步更新队列将Watcher去重批量执行,nextTick 基于微任务实现延迟回调到DOM更新后。

queueWatcher 入队

JavaScript
const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let waiting = false
let flushing = false

function queueWatcher(watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      // 正在flush时插入正确位置
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}

通过 has 对象去重,同一Watcher同一tick只入队一次,flush时按ID排序保证顺序。

flushSchedulerQueue 批量执行

JavaScript
function flushSchedulerQueue() {
  flushing = true
  
  // 按ID排序(父组件watcher先于子组件)
  queue.sort((a, b) => a.id - b.id)
  
  for (index = 0; index < queue.length; index++) {
    const watcher = queue[index]
    const id = watcher.id
    has[id] = null
    watcher.run()
  }
  
  // 重置状态
  waiting = false
  flushing = false
  queue.length = 0
  index = 0
}

遍历队列执行 watcher.run(),触发组件更新。

Watcher.run 执行

JavaScript
class Watcher {
  run() {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
        const oldValue = this.value
        this.value = value
        this.cb.call(this.vm, value, oldValue)
      }
    }
  }
  
  get() {
    pushTarget(this)
    const value = this.getter.call(this.vm, this.vm)
    return value
  }
}

run() 重新执行getter获取新值,对比变化后调用回调。

nextTick 实现

JavaScript
const callbacks = []
let pending = false

function nextTick(cb?: Function, ctx?: Object) {
  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
    })
  }
}

回调推入队列,同一tick多次调用只触发一次异步任务。

timerFunc 微任务降级

JavaScript
let timerFunc

// 1. Promise (微任务)
if (typeof Promise !== 'undefined') {
  const p = Promise.resolve()
  timerFunc = () => p.then(flushCallbacks)
}
// 2. 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)
  }
}
// 3. setTimeout (宏任务)
else {
  timerFunc = () => setTimeout(flushCallbacks, 0)
}

优先微任务保证DOM更新前执行,降级到setTimeout。

flushCallbacks 执行回调

JavaScript
function flushCallbacks() {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

使用副本执行防止执行期间新回调加入导致无限循环。

更新流程示例

JavaScript
// 同步代码
this.count = 1    // 触发setter -> watcher入队
this.count = 2    // 去重,不入队
this.count = 3    // 去重,不入队

// DOM不会立即更新
console.log(this.$el.textContent)  // 旧值

// 异步更新
// nextTick -> flushSchedulerQueue -> watcher.run() -> DOM更新

// nextTick回调
this.$nextTick(() => {
  console.log(this.$el.textContent)  // 新值
})

同一tick多次赋值只触发一次DOM更新,nextTick回调在更新后执行。

要点总结

  • queueWatcher 通过 has 对象去重,同一Watcher同一tick只入队一次
  • flush时按ID排序保证父组件先于子组件更新
  • watcher.run() 重新执行getter获取新值,对比变化调用回调
  • nextTick 将回调推入队列,同一tick多次调用只触发一次异步任务
  • timerFunc 优先微任务(Promise),降级到MutationObserver/setTimeout
  • flushCallbacks 使用副本执行防止执行期间新回调加入

📝 发现内容有误?点击此处直接编辑

← 上一篇 响应式数据与依赖追踪
下一篇 → 组件实例化与挂载流程
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库