可调度性指是我们可以控制副作用函数执行的时机、次数和方式,例如下面的例子:
let obj = { num: 1}effect(() => { console.log(proxyObj.num);})proxyObj.num++console.log('over');
目前的打印顺序是1、2、over,如果我们想要在不变动代码书写的顺序下让over先输出该怎么实现呢?这个时候就需要实现副作用函数的可调度性。
我们为effect设计一个选项参数options,允许用户指定调度器,调度器实际上就是一个函数:
effect(() => { console.log(proxyObj.num);},+ // options+ {+ // 调度器 scheduler 是一个函数+ scheduler(fn) {+ } }) -function effect(fn) {+ function effect(fn, options = {}) { // ...省略部分代码 effectFn.deps = []+ // 将 options 挂载到 effectFn 上+ effectFn.options = options effectFn()}
然后我们修改trigger函数,在具体执行副作用函数时,检查是否有调度器在副作用函数上,如果有的话,把副作用函数交给调度器来决定要怎么执行。
function trigger(target, key, newVal, receiver) { // ...省略部分代码- effectsToRun.forEach(fn => fn())+ effectsToRun.forEach(fn => {+ if (fn.options.scheduler) {+ fn.options.scheduler(fn)+ } else {+ fn()+ }+ })}
在具体的调度器函数实现中,我们用setTimeOut将副作用函数放到下一个任务队列中执行,如此就能让over先输出了。
effect(() => { console.log(proxyObj.num); }, // options { // 调度器 scheduler 是一个函数 scheduler(fn) {+ setTimeout(() => {+ fn()+ }) } })
运行结果如下,这边如果不是很理解为什么setTimeout就能实现晚一步输出的话,可以查阅一下一些写浏览器事件循环的文章,如果会的话就是我僭越了(红豆泥鞠躬♂️
effect(() => { console.log(proxyObj.num);})proxyObj.num++proxyObj.num++
在上面的例子中,修改了num值两次,会分别执行副作用函数,最终打印为1、2、3。
但在我们的期望中,第一次修改只是num值的过渡状态,我们并不关心,我们只关心他最终变成了3,我们希望的打印结果为1、3。
看到这应该很多人都很熟悉,在Vue中如果我们循环成千上万次更改变量的值,他也只会最后更新一次。
具体实现如下:
// 定义一个任务队列const jobQueue = new Set()// 使用Promise.resolve() 创建一个Promise示例,用他将函数添加到微任务队列const p = Promise.resolve()// 标识是否正在刷新队列let isFlushing = falsefunction flushJob() { // 如果队列正在刷新,则返回 if (isFlushing) return // 设置为true,表示正在刷新 isFlushing = true // 在微任务队列中依次执行jobQueue p.then(() => { jobQueue.forEach(job => job()) }).finally(() => { // 执行完jobQueue任务队列后重置isFlushing isFlushing = false })}effect(() => { console.log(proxyObj.num);}, { scheduler(fn) { // 将副作用函数放入jobQueue中 jobQueue.add(fn) // 调用flushJob刷新队列 flushJob() }})
分析上述代码:
最后的执行结果:
《Vue.js设计与实现》——响应式系统的可调度性
原文链接:
https://juejin.cn/post/7326367262659084315