精读《Vuejs设计与实现》days21

146 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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设计与实现》,真的值得一读