前文提要:
3.0 响应式系统的设计与实现*
3.1 一个稍微完善的Vue.js响应式系统
3.2 继续完善的Vue.js响应式系统
1、关于调度器的理解
从上述的章节中我们设计了一套响应式系统,但是这个系统有个问题是副作用函数是不可控的,即我们无法控制副作用函数的执行时机和执行次数。只能做到简单的触发-执行这样的流程。而调度器在系统设计中是非常常用的,一个系统运行其实就是一个一个功能的重复执行,而调度器的设计主要实现两个目的:
- 实现调度策略的灵活配置
- 资源的有效利用
在Vue.js的响应式系统中的副作用函数调度策略也是围绕这两点来设计的。
2、调度器的设计
假设有如下代码
// 省略部分代码
effect(() => {
console.log(obj.foo)
})
obj.foo++
console.log('结束啦')
此时的输出顺序为
1
2
结束啦
但是如果需要将输出的顺序改变为
1
结束啦
2
此时就需要调度一下副作用函数的执行时机,让输出顺序和上面提出的需求顺序相同。其实原理很简单,就是将需要延迟执行的部分放入微任务队列就行了,那么我们怎么设计调度器来执行这一过程呢,代码可以如下修改:
function effect(fn, options = {}) {
const effectFn = () => {
// 省略代码...
}
// 设置option选项,可以将调度器放入
effectFn.options = options
effectFn.deps = []
effectFn()
}
function trigger(target, key) {
// 省略代码 ...
// 将副作用函数追踪下来,防止出现set在删除时插入新值
effectsToRun.forEach(effectFn => {
// 检查是否有调度器,有调度器则执行调度器
if(effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}
effect(() => {
console.log(obj.foo)
}, {
// 设定调度函数
scheduler: (effectFn) => {
// 放入宏任务队列中,调整执行顺序
setTimeout(effectFn)
}
})
这样给effectFn
的参数中设置调度函数,通过调度函数设置执行的时机。
3、使用调度器节省副作用函数执行的资源消耗
在面试中常问的一个问题是,如响应式数据 x
绑定了副作用函数,要是循环执行x++
一万次,那么其副作用函数是否也会执行一万次。答案根据我们的直觉来说肯定是否定的,但是原理是什么呢?其实就是调度器的设置。
我们根据以上的代码测试一下
effect(() => {
console.log(obj.foo)
})
obj.foo++
obj.foo++
obj.foo++
console.log('结束啦')
输出的有
1
2
3
4
结束啦
其实这些输出结果中的2、3都是过渡状态,是没必要执行的,这些中间状态的控制用调度器是很容易实现的,在Vue中使用的是一个副作用函数的任务执行队列(Set
模拟),将要执行的副作用函数放入任务队列,但是任务队列的执行是在微任务队列中,这样就如上述所说的调整了执行的顺序。
所有过度状态的副作用函数会先触发,将要执行的副作用函数放入Set
中,由于Set
的自动去重机制会将重复的副作用函数去掉,这样就省略了过度状态,具体代码如下:
// 省略代码 ......
//副作用函数任务的队列
const jobQueue = new Set()
// 一个标记是否正在刷新队列
let isFlushing = false
// 将任务添加到微任务队列中
const p = Promise.resolve()
function flushJobs() {
// 如过正在执行副作用个函数队列直接跳出
if(isFlushing) return
// 标记队列执行的状态
isFlushing = true
p.then(() => {
jobQueue.forEach(job => job())
}).finally(() => {
// 执行完之后改变状态标记
isFlushing = false
})
}
effect(() => {
console.log(obj.foo)
}, {
scheduler: (effectFn) => {
// 添加到副作用函数任务队列中
jobQueue.add(effectFn)
// 刷新队列
flushJobs()
}
})
obj.foo++
obj.foo++
obj.foo++
其实这只是一个简单的调度器,主要是为了展示调度器的功能,即调整执行时机和节省资源,而Vue的完整调度器更加的完善和复杂。