• 3.3 具有调度功能的响应式设计


    前文提要:
    3.0 响应式系统的设计与实现*
    3.1 一个稍微完善的Vue.js响应式系统
    3.2 继续完善的Vue.js响应式系统

    1、关于调度器的理解

    从上述的章节中我们设计了一套响应式系统,但是这个系统有个问题是副作用函数是不可控的,即我们无法控制副作用函数的执行时机和执行次数。只能做到简单的触发-执行这样的流程。而调度器在系统设计中是非常常用的,一个系统运行其实就是一个一个功能的重复执行,而调度器的设计主要实现两个目的:

    1. 实现调度策略的灵活配置
    2. 资源的有效利用

    在Vue.js的响应式系统中的副作用函数调度策略也是围绕这两点来设计的。

    2、调度器的设计

    假设有如下代码

    // 省略部分代码
    effect(() => {
        console.log(obj.foo)
    })
    obj.foo++
    console.log('结束啦')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    此时的输出顺序为

    1
    2
    结束啦
    
    • 1
    • 2
    • 3

    但是如果需要将输出的顺序改变为

    1
    结束啦
    2
    
    • 1
    • 2
    • 3

    此时就需要调度一下副作用函数的执行时机,让输出顺序和上面提出的需求顺序相同。其实原理很简单,就是将需要延迟执行的部分放入微任务队列就行了,那么我们怎么设计调度器来执行这一过程呢,代码可以如下修改:

    function effect(fn, options = {}) {
        const effectFn = () => {
        	// 省略代码...
        }
    	// 设置option选项,可以将调度器放入
        effectFn.options = options
        effectFn.deps = []
        effectFn()
    }
    
    function trigger(target, key) {
        // 省略代码 ... 
        
        // 将副作用函数追踪下来,防止出现set在删除时插入新值
        effectsToRun.forEach(effectFn => {
            // 检查是否有调度器,有调度器则执行调度器
            if(effectFn.options.scheduler) {
                effectFn.options.scheduler(effectFn)
            } else {
                effectFn()
            }
        })
    } 
    
    effect(() => {
        console.log(obj.foo)
    }, { 
    	// 设定调度函数
        scheduler: (effectFn) => {
        	// 放入宏任务队列中,调整执行顺序
            setTimeout(effectFn)
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    这样给effectFn的参数中设置调度函数,通过调度函数设置执行的时机。

    3、使用调度器节省副作用函数执行的资源消耗

    在面试中常问的一个问题是,如响应式数据 x绑定了副作用函数,要是循环执行x++一万次,那么其副作用函数是否也会执行一万次。答案根据我们的直觉来说肯定是否定的,但是原理是什么呢?其实就是调度器的设置。

    我们根据以上的代码测试一下

    effect(() => {
        console.log(obj.foo)
    })
    obj.foo++
    obj.foo++
    obj.foo++
    console.log('结束啦')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    输出的有

    1
    2
    3
    4
    结束啦
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其实这些输出结果中的2、3都是过渡状态,是没必要执行的,这些中间状态的控制用调度器是很容易实现的,在Vue中使用的是一个副作用函数的任务执行队列(Set模拟),将要执行的副作用函数放入任务队列,但是任务队列的执行是在微任务队列中,这样就如上述所说的调整了执行的顺序。

    所有过度状态的副作用函数会先触发,将要执行的副作用函数放入Set中,由于Set的自动去重机制会将重复的副作用函数去掉,这样就省略了过度状态,具体代码如下:

    // 省略代码 ......
    
    //副作用函数任务的队列
    const jobQueue = new Set()
    // 一个标记是否正在刷新队列
    let isFlushing = false
    // 将任务添加到微任务队列中
    const p = Promise.resolve()
    
    function flushJobs() {
        // 如过正在执行副作用个函数队列直接跳出
        if(isFlushing) return
        // 标记队列执行的状态
        isFlushing = true
    
        p.then(() => {
            jobQueue.forEach(job => job())
        }).finally(() => {
            // 执行完之后改变状态标记
            isFlushing = false
        })
    }
    
    effect(() => {
        console.log(obj.foo)
    }, {
        scheduler: (effectFn) => {
            // 添加到副作用函数任务队列中
            jobQueue.add(effectFn)
            // 刷新队列
            flushJobs()
        }
    })
    
    obj.foo++
    obj.foo++
    obj.foo++
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    其实这只是一个简单的调度器,主要是为了展示调度器的功能,即调整执行时机和节省资源,而Vue的完整调度器更加的完善和复杂。

  • 相关阅读:
    SpringCloud - Spring Cloud 之 Gateway网关,Route路由,Predicate 断言,Filter 过滤器(十三)
    Linux网络-MAC协议
    React18学习
    JAVA观察者模式-案例
    吐槽 B 站收费,是怪它没钱么?
    Django连接MySQL数据库
    牛客小白月赛61-C-小喵觅食
    TS+Hooks二次封装antd Modal,实现可拖拽
    python枚举类定义和使用
    Android 发布 15 周年了!Google 员工说出了这些年的美好回忆
  • 原文地址:https://blog.csdn.net/yopilipala/article/details/131063744