Vue任务调度。
1、作用
vue中一个非常重要的功能,批量更新或者叫异步更新
响应式数据发生变化出发副作用函数重新执行时,我们有能力去决定副作用函数的执行时机、次数和方式。
2、例子
const state = reactive({
num: 1
})
effect(() => {
console.log('num', state.num)
})
state.num++
console.log('end')
3、实现可调度
const state = reactive({
num: 1
})
effect(() => {
console.log(state.num)
}, {
// 注意这里,假如num发生变化的时候执行的是scheduler函数
// 那么end将会被先执行,因为我们用setTimeout包裹了一层fn
scheduler (fn) {
// 异步执行
setTimeout(() => {
fn()
}, 0)
}
})
state.num++
console.log('end')
通过scheduler来自主控制副作用函数的执行时机。
在这之前,执行state.num++之后,console.log(state.num)将会被马上执行,而添加scheduler后,num发生变化后将执行scheduler中的逻辑。
4、实现批量更新 & 异步更新
来看段诡异的代码,请问num会被执行多少次?100还是101?
const state = reactive({
num: 1
})
effect(() => {
console.log('num', state.num)
})
let count = 100
while (count--) {
state.num++
}
对于页面渲染来说1到101中间的2~100仅仅只是过程,并不是最终的结果,处于性能考虑Vue只会渲染最后一次的101。
Vue是如何做到的呢?
利用可调度性,再加点事件循环的知识,我们就可以做到这件事。
- num的每次变化都会导致scheduler的执行,并将注册好的副作用函数存入jobQueue队列,因为Set本身的去重性质,最终只会存在一个fn
- 利用Promise微任务的特性,当num被更改100次之后同步代码全部执行结束后,then回调将会被执行,此时num已经是101,而jobQueue中也只有一个fn,所以最终只会打印一次101
const state = reactive({
num: 1
})
const jobQueue = new Set()
const p = Promise.resolve()
let isFlushing = false
const flushJob = () => {
if (isFlushing) {
return
}
isFlushing = true
// 微任务
p.then(() => {
jobQueue.forEach((job) => job())
}).finally(() => {
// 结束后充值设置为false
isFlushing = false
})
}
effect(() => {
console.log('num', state.num)
}, {
scheduler (fn) {
// 每次数据发生变化都往队列中添加副作用函数
jobQueue.add(fn)
// 并尝试刷新job,但是一个微任务只会在事件循环中执行一次,所以哪怕num变化了100次,最后也只会执行一次副作用函数
flushJob()
}
})
let count = 100
while (count--) {
state.num++
}
长风破浪会有时,直挂云帆济沧海