Appearance
异步更新
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
}