Vue异步更新队列源码解析
此文章仅代表个人的阅读理解,如有不对欢迎指出
队列:就简单的理解为一个数组即可,不要想的太复杂
整个异步更新过程大致可以理解为
1、当数据发生改变会先触发defineReactive方法里面定义的set方法
2、set方法里面对调用 dep.notify() (内部就是遍历收集到的观察者的update方法)
3、watcher.update 方法被执行
4、通过queueWatcher方法将观察者推入到queue队列里面
5、queue队列是通过flushSchedulerQueue方法执行的
7、将flushSchedulerQueue方法通过nextTick推入到callbacks中执行
8、最终会由timerFunc方法执行
对应的源码如下:
defineReactive
// defineReactive 是整个响应式系统中最核心的方法
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// ...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 被get的时候收集依赖即Dep.target
dep.depend()
//...
}
return value
},
set: function reactiveSetter (newVal) {
// ...
// dep.notify 就是通知所有的观察者更新
dep.notify()
}
})
}
// Dep.notify定义
export default class Dep {
static target: ?Watcher;
id: number;
// subs就是当前收集到的依赖
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
//...
// 通知所有的watcher更新
notify () {
// ...
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// Wacther的update方法定义
class Watcher {
//...
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
// 推入到queue队列
queueWatcher(this)
}
}
}
callbacks 异步回调
// 可以看到其实就是个普通的数组
const callbacks = []
// 这个方法主要是用来添加任务
// 包括Vue.nextTick也是调用的这个方法实现
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 这里主要是把回调函数添加到callbacks
callbacks.push(() => {
//省略其他代码...
cb.call(ctx)
})
// 这个pending会在 flushCallbacks 方法里面被修改, 即在队列被刷新或执行的时候
if (!pending) {
pending = true
// 这个就是一个异步的函数,详见下面
timerFunc()
}
//省略其他代码 ...
}
// timerFunc 是实现异步的核心方法
// 优先使用微任务,但在某些情况下会使用宏任务
let timerFunc
// 1、优先使用Promise微任务执行回调(这里在2.5版本以前实现略有不同)
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// 这里好像是对移动平台的一些特殊处理,强制刷新队列
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
// 2、 在不支持Promise的情况下的优先选择
// MutationObserver: https://developer.mozilla.org/zh-cn/docs/web/api/mutationobserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
//...
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
// 3、这里其实就是利用宏任务队列了
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 4、使用setTimeout进行兜底
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
- 到这里应该能大概理解vue里面是怎么执行异步任务的了
- 下面就看vue是如何执行queue队列的
queue 队列定义如下
// 可以看到queue队列就是一个Watcher类型的数组
// Watcher的定义这里就不做过多赘述了,想了解的同学可以看 core/observer/watcher.js
const queue: Array<Watcher> = []
// 这里Watcher主要做两件事
// 1、将watcher压入队列,且重复的只会被压入一次
// 2、例用nextTick将flushSchedulerQueue丢到异步任务里面执行
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 这里has[id]应该是为了防止重复添加,在被执行的时候会被重置为null
// 也可以理解为等待执行的一个状态
if (has[id] == null) {
has[id] = true
if (!flushing) {
// 推入队列
queue.push(watcher)
} else {
// 如果当前的队列正在被执行,那么会根据id进行顺序插入
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// 开始执行队列 waiting会在reset方法里面被重置
if (!waiting) {
waiting = true
// 在开发环境 并且配置了async那么会立即执行 flushSchedulerQueue
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
// 生产环境会将当前的队列执行函数推入到异步任务中即callbacks
// nextTick 就是上面提到的nextTick
nextTick(flushSchedulerQueue)
}
}
}