Skip to content

异步更新

javascript
// 通过队列 queue 保存需要调用 run 方法 的 watcher,通过对象 has 进行
// watcher 去重实现同一个数据多次更新时只会调用该 watcher 一次 run 方法,通过变量
// waiting 作为开始清空队列的开关,默认是 false,通过 flushSchedulerQueue 清空队列,
// 内部调用遍历每一个 watcher 并调用 run 方法。要实现异步更新,我们只需要在异步任务里
// 执行 flushSchedulerQueue 即可。因此当执行 update 时,调用 queueWatcher(this),
// 内部取出 watcher.id,如果不存在才添加进 queue,再判断开始是否是关闭,如果是,
// 在异步任务中执行 flushSchedulerQueue,下一行通过同步代码把开关的状态变为 true,
// 这样只有在所有同步任务执行完后,也就是所有 watcher.update 执行完后,最后才在异步
// 任务中调用 flushSchedulerQueue,实现了异步更新。这里的异步任务并不是一个定时器,
// 如果写成定时器可以实现异步更新,但使用者想获取更新后的 DOM 也需要写进定时器中,
// 于是封装一个 nextTick 方法,将多个异步任务变为一个异步任务。

let queue = [], // 需要update的watcher
  has = {}, // 去重
  waiting = false // 是否开始清空队列的开关

function queueWatcher(watcher) {
  let id = watcher.id

  if (has[id] == null) {
    // 同一个数据多次更新只会更新一次
    // vm.name = 'foo'
    // vm.name = 'bar'
    // vm.name = 'baz' // 同一个数据多次更新时只需要最后一次更新一次视图即可
    queue.push(watcher) 
    has[id] = true 
  }

  if (!waiting) {
    // 封装一个nextTick, 将多个异步任务转为一个
    nextTick(flushShedulerQueue
      queue[i].run
    )
    waiting = true
  }
}

function flushSchedulerQueue () { // 清空watcher队列
  for (let i = 0, l = queue.length; i < l; i++) {
    // 更新
    queue[i].run()
  }

  // 回到初始化
  queue = []
  has = {}
  waiting = false
}

// nextTick 内部的逻辑和 queueWatcher 的逻辑是一样的,通过 callbacks 维护所有的回调,
// 即 flushShedulerQueue 清空 watcher 队列的回调 + 使用者使用 nextTick 定义的回调,
// 通过变量 pending 作为清空回调的开关,默认是 false,通过 flushCallbacks 清空回调,
// 即调用每一个回调,通过 timerFunc 作为异步任务,内部调用 flushCallbacks。
// timerFunc 做了兼容性处理,优先级是
// Promise.then > MutationObserver > setImmediate > setTimeout,
// Vue3中不做兼容性,直接 Promise.then。nextTick 中如果发现开关是关闭的,则执行异步
// 任务 timerFunc,下一行通过同步代码将开关变为 true,这样回调就会最后执行,由于开发者
// 先修改数据,再定义 nextTick 回调,因此会先在 nextTick 中先执行 queueWatcher。再执行
// 开发者的 nextTick,flushCallbacks 中的顺序先是 flushSchedulerQueue,然后是我们
// 定义的异步任务,保证了会先更新 DOM 后再执行我们定义的 nextTick 回调,保证了我们定义
// 的 nextTick 回调执行时 DOM 都已更新完毕。
fucntion nextTick (cb) {
    callbacks.push(cb)
    if (!pending) {
      timerFunc() 
      pending = true
    }
}

let callbacks = [],
    pending = false,
    timerFunc // 清空callbacks的函数, callbacks = watcher队列 + 用户使用nextTick回调

if (typeof Promise !== 'undefined') { // timerFunc是异步任务
  let p = Promise.resolve()
  timerFunc = function () {
    p.then(flushCallbacks)
  }
} else if (typeof MutationObserver !== 'undefined') {
  let counter = 1,
  observer = new MutationObserver(flushCallbacks),
  textNode = document.createTextNode(String(counter))
  // 监控DOM, 监控属性变化, 触发回调
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = function () {
    counter = (counter + 1) >> 1
    textNode.data = String(counter)
  }
} else if (typeof setImmediate !== 'undefined'){
  timerFunc = function () {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = function () {
    setTimeout(flushCallbacks, 0)
  }
}

function flushCallbacks () {
  // 用户使用 nextTick, 回调会在 watcher 后面, 保证获取的数据是最新的
  for (let i = 0, l = callbacks.length; i < l; i++) {
    callbacks[i]()
  }

  callbacks = []
  pending = false
}