《Vue.js设计与实现》——响应式系统的可调度性

《Vue.js设计与实现》------响应式系统的基本实现

《Vue.js设计与实现》------分支切换与 cleanup

《Vue.js设计与实现》------副作用函数实现可嵌套

最好预备了解的知识点

  • 浏览器事件循环

可调度性的必要性

可调度性指是我们可以控制副作用函数执行的时机、次数和方式,例如下面的例子:

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()
    }
})

分析上述代码:

  1. jobQueue中的副作用函数的遍历执行被放在了p的then的回调函数中,也就是在被放在了微任务队列执行,所以会在更改了多次值的后面再执行副作用函数;
  2. 副作用函数被添加进了jobQueue变量中,因为这个变量使用Set定义的,可以去重 里面的元素,所以如果你重复修改同一个值,多次把同一个副作用函数添加进jobQueue中,最后在变量中也只有一个关联的副作用函数。

最后的执行结果:

小结

这一篇文章中我们初步认识了响应式系统的调度执行 ,下两篇文章中我们会尝试实现computed 和watch,有什么写的不对的地方或者什么疑问可以留言,希望文章有帮助到你~

参考书籍

《Vue.js设计与实现》------------霍春阳

相关推荐
莫西干式秃头14 分钟前
用 AI + threejs 写了个浏览器里的 3D 动作游戏《武来》:砍怪、放技能、开 SU7
前端·人工智能·three.js
kyriewen28 分钟前
我测了 6 种防请求竞态的写法:只有 1 种真能打
前端·javascript·程序员
孟健30 分钟前
用 WorkBuddy 做个人名片网页,挂上开源设计技能后效果完全不同了
前端·人工智能·ai编程
IT_陈寒34 分钟前
SpringBoot自动配置的坑,这次终于爬出来了
前端·人工智能·后端
LuHang2 小时前
从天地图切换到高德地图:一次真实的底图迁移实战
前端·vue.js·cesium
用户360055579002 小时前
Kestrel_VLLM - 27 · 寄存器常驻与融合发射:两次「少发一次」
前端
前端小万2 小时前
用"道法术器"重构你和 AI 的协作方式
前端·人工智能·程序员
鶴哥只手遮天2 小时前
Angular 22 源码级深度解析-第 06 章 模板指令全解:`ɵɵelementStart`、`ɵɵproperty`、`ɵɵadvance` 与绑定指令
前端·开源·angular.js
像我这样帅的人丶你还2 小时前
我html生成的doc文档为何在手机文档显示不了图片?
前端·javascript
前端双越老师3 小时前
程序员 1v1 访谈系列第十期:纯前端坚持不了多久了
前端·ai编程·全栈