文章参考了霍春阳的《Vue.js设计与实现》,是自己在阅读过程中的一些思考和理解
调度执行
目的是为了让副作用函数拥有可调度性,包括决定副作用函数的执行时机、次数以及方式。
可以给effectfn上添加一个options属性,允许用户指定调度器。即给effect函数添加第二个options参数,将其挂载到effectfn上。代码如下:
function effect(fn, options = {}) {
const effectfn = () => {
clearup(effectfn)
activeEffect = effectfn
// 栈设计
effectStack.push(effectfn)
fn()
effectStack.pop()
activeEffect = effectStack[effectStack.length-1]
}
// 新增调度选项挂载
effectfn.options = options
effectfn.deps = []
effectfn()
}
时机控制
在tirgger函数的执行过程中如果副作用函数存在调度器,则将副作用函数作为参数传递给调度器,进而让用户自己能够决定怎么来执行trigger。代码如下:
effectsToRun.forEach(effect => {
// 如果存在调度器,则交给调度器执行,否则按默认行为执行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
次数控制
在调度器的基础上新增任务队列,代码如下:
// 任务队列
const queue = new Set()
// 使用Promise.resolve()创建一个Promise实例,用它将任务添加到微任务队列
const p = Promise.resolve()
// 标志代表是否正在刷新对了
let isFlushing = false
function flushJob() {
// 如果正在刷新,则直接返回
if(isFlushing) return
// 加锁
isFlushing = true
// 在微任务队列中刷新queue队列
p.then(() => {
flushJob.forEach(job => job())
}).finally(() => {
// 结束后重置
isFlushing = false
})
}
effect(() => {
console.log(obj.num)
}, {
scheduler(fn) {
queue.add(fn)
flushJob()
}
})
目前的问题:假设要连续增加一个数的值,但是我们只想看到最初和最后的值。
首先定义了一个queue队列,用的Set集合,利用其特性可以实现自动去重。在trigger执行时,发现副作用函数有调度器,将函数扔给调度器执行,调度器中添加该函数,然后执行flushJob函数。通过isFlushing判断当前函数是否已经在执行了,这个标志是为了让函数中一个周期内,只执行一次而不是对每个副作用函数都执行。而副作用函的执行是在promise的微队列中进行。
因此整体的看,对于自增的操作,每次的自增都会调用trigger,但是有调度器的Set集合存在,相同的effectfn函数不会添加到队列中;而flushJob函数由于有isFlushing标志位的存在,页不会每次都执行;最后由于Promise微队列的存在,使得在最后才会执行effectfn,且只会执行一次,而当它执行时,自增操作已经结束了,进而我们只会看到最初和最后的值。最初的值是在effect函数中执行看到的。
这个功能类似在Vue中的连续修改响应式的值,但数据只会触发一次更新。思路是相同的,这里需要看源码。