《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() }) // 删除
}

最后的运行结果:

小结

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

相关推荐
二十雨辰1 分钟前
[uni-app]小兔鲜-06地址+sku+购物车
前端·javascript·vue.js·uni-app
码农00018 分钟前
Vue2 + ElementUI + axios + VueRouter入门
前端·javascript·elementui
吕永强1 小时前
CSS概述
前端·css·css3
古韵1 小时前
用alovajs的use-fetcher,轻松搞定数据请求难题
前端·javascript·axios
秃头女孩y1 小时前
React基础-快速梳理
前端·react.js·前端框架
DT——1 小时前
vue中如何实现组件通信
前端·javascript·vue.js
泯泷2 小时前
「生产必看」在企业环境中正确使用 Node.js 的九大原则
前端·后端·node.js
米饭「」2 小时前
C语言语句、语句分类及注释
java·c语言·前端
开心工作室_kaic2 小时前
基于微信小程序的校园失物招领系统的设计与实现(论文+源码)_kaic
c语言·javascript·数据库·vue.js·c#·旅游·actionscript
Small-K2 小时前
前端框架中@路径别名原理和配置
前端·webpack·typescript·前端框架·vite