持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情
4.7 调度执行
上文中,我们实现了基本的调度执行,但是还缺少对执行次数的控制,或者说副作用的合并。比如这样的操作
const data = reactive({num:0})
effect(()=>{
console.log(data.num)
})
data.num++
data.num++
这里执行了2次data.num++,应该会显示
0
1
2
但实际上,我们直接显示2不就好了,何必再显示一次1呢,显示一次就意味着执行一次副作用函数,也意味着性能的消耗。 我们希望能减少一次执行,直接显示
0
2
有了调度器,我们可以很简单的实现这个功能
const jobs = new Set()
let isRunning = false
const flushJob = ()=>{
//当前有执行中任务则退出
if(isRunning){return}
isRunning = true
//把这些操作放到微任务队列中执行
Promise.resolve()
.then(()=>{
jobs.forEach(job=>job())
})
.finally(()=>{
isRunning = false
})
}
我们这里首先定义一个Set,之所以用Set其实也就是省去一个去重的过程,其实function也可以通过set去重,这一点前面我们用来很多次了.
在我们定义了一个状态值isRunning,这里其实是为了节流,我们需要在一个周期内只执行一次。
在之后就是通过Promise把任务放到微任务中执行,避免阻塞页面。
而使用的时候,只需要在effect中传入调度器既可
effect(()=>{
console.log(data.num)
},{
scheduler(fn){
jobs.add(fn)
flushJob()
}
})
当我们执行这句代码的时候,调度内其实已经对副作用函数进行了去重,2次执行变成了1次,因此最后只会输出2次,不再是3次,减少了一次执行。
看到这里,有没有感觉这个操作很熟悉。在vue文档中说到
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
大概的原理就是这样了,只不过vue代码中肯定有很多条件判断,边界验证,保证代码的严谨性,而我这里只是实现了基本功能罢了。
讲到这里,其实reactive的基本功能都实现了80%了,书里这样由浅入深的讲解实在是让我受益匪浅。我们可以跟随vueJs框架设计者的步伐,去实现响应式,看到他们的思路。
再次强烈推荐《Vue.js设计与实现》,真的值得一读