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 函数,提升灵活性与可扩展性。

相关推荐
小赖同学啊6 分钟前
光伏园区3d系统管理
前端·javascript·3d
前端进阶者14 分钟前
js通知提醒
前端·javascript
快起来别睡了26 分钟前
看完你就知道JavaScript 中的对象创建与继承方式原来这么简单?!
javascript
乌兰麦朵36 分钟前
Vue吹的颅内高潮,全靠选择性失明和 .value 的PUA!
前端·vue.js
Goodbaibaibai36 分钟前
创建一个简洁的Vue3 + TypeScript + Vite + Pinia + Vue Router项目
javascript·vue.js·typescript
流星稍逝41 分钟前
用vue3的写法结合uniapp在微信小程序中实现图片压缩、调整分辨率、做缩略图功能
前端·vue.js
OEC小胖胖1 小时前
深入理解 Vue.js 响应式原理及其在 Web 前端开发中的应用
开发语言·前端·javascript·vue.js·web
断竿散人1 小时前
JavaScript 错误对象完全指南:内置类型解析与自定义异常实战
前端·javascript
默默地离开1 小时前
Blob二进制处理的王者
前端·javascript·html
今禾1 小时前
深入理解 JavaScript 事件监听机制
前端·javascript