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

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

在上一篇文章中我们实现了一个Vue的最基本的响应式系统,这节我们来尝试解决其中存在的一个问题------分支切换。

分支切换例子

javascript 复制代码
// 响应式系统的基本实现代码参考上一节《Vue.js设计与实现》------响应式系统的基本实现
let obj = { text: 'hello responsive data', ok: true }

watchEffectFn(() => {
    console.log('effect func');
    document.body.innerText = proxyObj.ok ? proxyObj.text : 'not'
})

该例子初始状态下,因为ok的值是true,所以oktext两个字段都会被读取,即两个字段都会和该副作用函数建立联系,然后我们按下面的步骤更改值:

  1. ok的值改为false
  2. text的值改为'change text'
javascript 复制代码
setTimeout(() => {
    proxyObj.ok = false
}, 2000)

setTimeout(() => {
    proxyObj.text = 'change text'
}, 3000)

我们期望的结果是:

  1. ok的值变为false,触发执行副作用函数,页面上的值变为not,控制台打印'effect func'
  2. text的值更改,但因为ok的值是false,text的值已经并不会被访问到,所以就算text值变动了,页面应无变动,控制台应无新增输出,即这个副作用函数没有执行的必要

但实际上更改text的值后还是会触发执行副作用函数,控制台会多一次输出

解决分支切换问题

分析

在上面的的例子中,出现非期望的情况的原因是:text字段和副作用函数还存在着联系,即在修改值后触发trigger执行时查找出来的副作用函数集合中还有该副作用函数。

所以解决办法就是每次在执行副作用函数之前,切断这个副作用函数和所有字段的联系 ,然后在执行过程中再重新建立字段和副作用函数之间的联系,这样字段和副作用函数之间的联系就是全新且正确的了。

切断这个副作用函数和所有字段的联系 用代码的说法就是:在所有的Set集合中,把这个副作用函数删除,那么我们就需要:

  1. 收集这个副作用函数关联了哪些Set
  2. 在执行函数前遍历这些Set,删除当中的对应副作用函数

下面我们尝试实现这两步。

实现

收集这个副作用函数关联了哪些Set集合

diff 复制代码
// 重写watchEffectFn函数
function watchEffectFn(fn) {
    // 实际的副作用函数被包裹了一层
    const effectFn = () => {
        activeEffectFn = effectFn
        fn()
    }
    effectFn.deps = []
    effectFn()
}

function track(target, key, receiver) {
    // ...前面代码省略
+   // 这边记录副作用函数在哪些Set中出现过
+   activeEffectFn.deps.push(deps)
}

清除副作用函数和Set集合之间的联系

diff 复制代码
function watchEffectFn(fn) {
    const effectFn = () => {
+        // 执行副作用函数前清除和Set集合之间的联系 
+        cleanup(effectFn)
        activeEffectFn = effectFn
        fn()
    }
    effectFn.deps = []
    effectFn()
}

cleanup函数的实现如下:

javascript 复制代码
function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        let deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}

缺陷修复(无限执行)

大功告成,运行代码,看看效果。好好好,修改ok的值时第一次触发set直接进入无限执行打印(🤯

问题出在cleanup和trigger中的以下循环执行副作用函数的逻辑冲突:

javascript 复制代码
effects && effects.forEach(fn => { fn() })

我们来梳理一下问题出现的执行过程:

  1. 修改ok的值,触发set,进入forEach循环,遍历执行副作用函数fn。
  2. 执行fn的过程中会执行cleanup函数清除联系,然后执行具体函数过程时又重新建立联系添加到集合中,相当于边删除边添加,由此就导致了无限执行。

语言规范中对此有明确的说明:在调用 forEach 遍历 Set 集合 时,如果一个值已经被访问过了,但该值被删除并重新添加到集合, 如果此时 forEach 遍历没有结束,那么该值会重新被访问。---------《Vue设计与实现》

具体修改如下, 具体执行时创建集合的副本来执行:

diff 复制代码
function trigger(target, key, newVal, receiver) {
    const depsMap = targetBucket.get(target)
    if (!depsMap) return
    const effects = depsMap.get(key)

+    const effectsToRun = new Set(effects) // 新增
+    effectsToRun.forEach(fn => fn()) // 新增
-    effects && effects.forEach(fn => { fn() }) // 删除
}

最后的运行结果:

小结

由此我们就解决了响应式系统中的分支切换问题,有什么写的不对的地方或者什么疑问可以留言,希望文章有帮助到你~

相关推荐
yq19820430115640 分钟前
使用Django构建视频解析网站 从Naver视频下载器看Web开发全流程
前端·django·音视频
李明卫杭州1 小时前
在 JavaScript 中,生成器函数(Generator Function)
前端·javascript
Lethehong1 小时前
从安装到实测:基于 Claude Code + GLM-4.7 的前端生成与评测实战
前端
恋猫de小郭2 小时前
iOS + AI ,国外一个叫 Rork Max 的项目打算替换掉 Xcode
android·前端·flutter
宇木灵2 小时前
C语言基础-三、流程控制语句
java·c语言·前端
qq8406122332 小时前
Nodejs+vue基于elasticsearch的高校科研期刊信息管理系统_mb8od
前端·vue.js·elasticsearch
哆啦A梦15884 小时前
Vue3魔法手册 作者 张天禹 012_路由_(一)
前端·typescript·vue3
RaidenLiu5 小时前
别再手写 MethodChannel 了:Flutter Pigeon 工程级实践与架构设计
前端·flutter·前端框架
~央千澈~6 小时前
抖音弹幕游戏开发之第17集:添加日志系统·优雅草云桧·卓伊凡
linux·服务器·前端
JamesYoung79716 小时前
第一部分 — 基础知识 项目框架与文件布局
前端·chrome