Vue.js 3 渐进式实现之响应式系统——第三节:建立副作用函数与被操作字段之间的联系

往期回顾

  1. 系列开篇与响应式基本实现
  2. effect 函数注册副作用

建立副作用函数与被操作字段之间的联系

现在我们的响应式系统还没有在副作用函数与被操作的目标字段之间建立明确联系:

  • 当读取属性时,无论读取的是哪个属性,都一样,都会把副作用函数收集到"桶"里。
  • 当设置属性时,无论设置的是哪个属性,也都会把"桶"里的副作用函数取出并执行。

解决这个问题需要重新设计"桶"的数据结构,而不能简单地使用一个 Set 了。

思路

观察下面代码

javascript 复制代码
effect(function effectFn() {
    document.body.innerText = obj.text
})

这段代码存在三个角色:

  • 被操作 (读取) 的代理对象 obj;
  • 被操作 (读取) 的字段名 text;
  • 使用 effect 函数注册的副作用函数 effectFn。

如果用 target 表示被代理对象代理的原始对象,key 表示 被操作的字段名,effectFn 表示被注册的副用函数,可以为三个角色建立如下关系:

如果有两个副作用函数同时读取同一个对象的同一个属性值:

如果一个副作用函数读取了一个对象的两个不同属性值:

再如果两个副作用函数读取了两个不同对象中的属性值:

代码

javascript 复制代码
// 用一个全局变量存储被注册的副作用函数
let activeEffect
// effect 函数用于注册副作用函数
function effect(fn) {
    // 当调用 effect 注册副作用函数时,将副作用函数 fn 赋值给 activeEffect
    activeEffect = fn
    fn()
}

// 存储副作用函数的桶
const bucket = new WeakMap()

// 原始数据
const data = { text: 'hellow world' }
// 对原始数据的代理
const obj = new Proxy(data, {
    // 拦截读取操作
    get(target, key) {
        // 没有 activeEffect 直接 return
        if (!activeEffect) return terget[key]

        // 根据 target 从"桶"中取得 depsMap,也是Map类型:key --> effects
        let depsMap = bucket.get(target)
        // 如果不存在 depsMap,就新建一个 Map 并与 target 关联
        if (!depsMap) {
            depsMap = new Map()
            bucket.set(target, depsMap)
        }

        // 再根据 key 从 depsMap 中取得 deps。
        // deps是一个 Set 类型,储存所有与当前 key 相关联的副作用函数
        let deps = depsMap.get(key)
        // 如果 deps 不存在,同样新建一个 Set 并与 key 关联
        if (!deps) {
            deps = new Set()
            depsMap.set(key, deps)
        }

        // 最后将当前激活的副作用函数添加到"桶"里
        deps.add(activeEffect)

        // 返回属性值
        return target[key]
    },
    // 拦截设置操作
    set(target, key newVal) {
        // 设置属性值
        target[key] = newVal
        // 把副作用函数从桶里取出并执行
        bucket.forEach(fn => fn())
        // 返回 true 代表设置操作成功
        return true
    }
})

已实现

我们分别使用了 WeakMap,Map 和 Set,建立起副作用函数与被操作字段之间的精准联系:

  • WeakMap 由 target --> Map 构成,键是原始对象 target,值是一个 Map。
  • Map 由 key --> Set 构成,键是原始对象 target 的 key,值是储存副作用函数的 Set。

缺陷/待实现

get 函数中代码逻辑很复杂,可以把依赖收集的逻辑单独封装一个 track 函数;set 中依赖触发的逻辑也可以单独封装一个 trigger 函数,提升灵活性与可扩展性。

相关推荐
摸鱼的春哥25 分钟前
惊!黑客靠AI把墨西哥政府打穿了,海量数据被黑
前端·javascript·后端
小兵张健27 分钟前
Playwright MCP 截图标注方案调研(推荐方案1)
前端·javascript·github
我叫黑大帅3 小时前
Vue3和Uniapp的爱恨情仇:小白也能懂的跨端秘籍
前端·javascript·vue.js
None3213 小时前
【NestJs】使用Winston+ELK分布式链路追踪日志采集
javascript·node.js
Qinana4 小时前
从代码到智能体:MCP 协议如何重塑 AI Agent 的边界
前端·javascript·mcp
洋洋技术笔记4 小时前
Vue实例与数据绑定
前端·vue.js
Marshall1514 小时前
zzy-scroll-timer:一个跨框架的滚动定时器插件
前端·javascript
明月_清风6 小时前
打字机效果优化:用 requestAnimationFrame 缓冲高频文字更新
前端·javascript
明月_清风6 小时前
Markdown 预解析:别等全文完了再渲染,如何流式增量渲染代码块和公式?
前端·javascript
牛奶15 小时前
Vue 基础理论 & API 使用
前端·vue.js·面试