最好预备了解的知识点
- 浏览器事件循环
可调度性的必要性
可调度性指是我们可以控制副作用函数执行的时机、次数和方式,例如下面的例子:
javascript
let obj = {
num: 1
}
effect(() => {
console.log(proxyObj.num);
})
proxyObj.num++
console.log('over');
目前的打印顺序是1、2、over ,如果我们想要在不变动代码书写的顺序下让over先输出该怎么实现呢?这个时候就需要实现副作用函数的可调度性。
具体实现控制执行时机
我们为effect
设计一个选项参数options ,允许用户指定调度器,调度器实际上就是一个函数:
diff
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
函数,在具体执行副作用函数时,检查是否有调度器 在副作用函数上,如果有的话,把副作用函数交给调度器来决定要怎么执行。
diff
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先输出了。
diff
effect(() => {
console.log(proxyObj.num);
},
// options
{
// 调度器 scheduler 是一个函数
scheduler(fn) {
+ setTimeout(() => {
+ fn()
+ })
}
})
运行结果如下,这边如果不是很理解为什么setTimeout
就能实现晚一步输出的话,可以查阅一下一些写浏览器事件循环的文章,如果会的话就是我僭越了(红豆泥鞠躬🙇♂️
利用调度器控制副作用函数的执行次数
javascript
effect(() => {
console.log(proxyObj.num);
})
proxyObj.num++
proxyObj.num++
在上面的例子中,修改了num值两次,会分别执行副作用函数,最终打印为1、2、3。
但在我们的期望中,第一次修改只是num值的过渡状态,我们并不关心,我们只关心他最终变成了3,我们希望的打印结果为1、3。
看到这应该很多人都很熟悉,在Vue中如果我们循环成千上万次更改变量的值,他也只会最后更新一次。
具体实现如下:
javascript
// 定义一个任务队列
const jobQueue = new Set()
// 使用Promise.resolve() 创建一个Promise示例,用他将函数添加到微任务队列
const p = Promise.resolve()
// 标识是否正在刷新队列
let isFlushing = false
function 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()
}
})
分析上述代码:
jobQueue
中的副作用函数的遍历执行被放在了p
的then
的回调函数中,也就是在被放在了微任务队列执行,所以会在更改了多次值的后面再执行副作用函数;- 副作用函数被添加进了
jobQueue
变量中,因为这个变量使用Set
定义的,可以去重 里面的元素,所以如果你重复修改同一个值,多次把同一个副作用函数添加进jobQueue
中,最后在变量中也只有一个关联的副作用函数。
最后的执行结果:
小结
这一篇文章中我们初步认识了响应式系统的调度执行 ,下两篇文章中我们会尝试实现computed 和watch,有什么写的不对的地方或者什么疑问可以留言,希望文章有帮助到你~
参考书籍
《Vue.js设计与实现》------------霍春阳