vue3 存储依赖的数据结构

news/2025/2/8 11:25:23/文章来源:https://www.cnblogs.com/is-DW/p/18703892

WeakMap 和 Map 详解

1. Map

定义
Map 是一种键值对的集合,允许使用任意类型的值(对象、原始类型)作为键,保持插入顺序,支持遍历和大小查询。

核心特性

  • 键的多样性:键可以是任意数据类型(包括对象、函数等),通过严格相等比较(===)来匹配。
  • 顺序性:插入顺序被保留,迭代时按插入顺序返回键值对。
  • 内存管理:若键是对象,即使该对象在其他地方被销毁,Map 仍保留其引用,可能导致内存泄漏。
  • 方法和属性
    • set(key, value):添加键值对。
    • get(key):获取对应值。
    • has(key)delete(key)clear()
    • size:返回元素数量。
    • keys()values()entries():返回迭代器。

使用场景

  • 需要复杂键(如对象关联数据)。
  • 需要维护插入顺序(如历史记录)。
  • 需要频繁遍历或查询大小。

示例

const map = new Map();
const keyObj = { id: 1 };map.set(keyObj, 'Value for object');
console.log(map.get(keyObj)); // 'Value for object'// 迭代示例
for (const [key, value] of map) {console.log(`${key}: ${value}`);
}

2. WeakMap

定义
WeakMap 是一种特殊的键值对集合,键必须是对象,值可以是任意类型。键采用弱引用,不会阻止垃圾回收(GC)。

核心特性

  • 弱引用键:若键对象没有其他引用,即便在 WeakMap 中存在,也会被 GC 回收。
  • 不可枚举性:不支持遍历(如 keys()values())和 size 查询。
  • 自动清理:键对象被回收后,对应的键值对会被自动移除。

方法和属性

  • set(key, value):仅接受对象作为键。
  • get(key)has(key)delete(key)
  • 注意:无 clear()size 或迭代方法。

使用场景

  • 存储对象的私有数据或元数据(避免内存泄漏)。
  • 缓存临时对象关联的信息(如 DOM 元素附加数据)。

示例

const weakMap = new WeakMap();
let obj = { id: 1 };weakMap.set(obj, 'Private data');
console.log(weakMap.get(obj)); // 'Private data'// 当 obj 被设为 null,且无其他引用时,GC 会自动回收该键值对。
obj = null;

3. 对比 Map 和 WeakMap

特性 Map WeakMap
键类型 任意类型 仅对象
垃圾回收影响 阻止键对象的回收(强引用) 不阻止(弱引用)
可枚举性 支持遍历和 size 不可枚举
方法完整性 完整的方法(如 clear() 仅有 set/get/has/delete
典型使用场景 通用键值存储、需遍历的数据 对象关联元数据、内存敏感场景

4. 常见问题

Q1: 何时优先选用 WeakMap?
当需要存储与对象生命周期绑定的私有数据时(例如类实例的隐藏属性),WeakMap 能自动清理数据,避免内存泄漏。

Q2: 为什么 WeakMap 不支持遍历?
弱引用的特性导致键可能随时被回收,无法保证迭代时所有键都存在,因此设计上不支持遍历。

Q3: WeakMap 如何用于缓存?
若缓存键是对象且希望对象销毁时缓存失效,WeakMap 可避免手动清理缓存。例如保存 DOM 元素的计算结果,元素移除后缓存自动删除。

在 Vue3 的响应式系统中,依赖收集是其核心机制之一,通过 WeakMap + Map + Set 组合的层级结构来管理依赖关系(称为 targetMap)。下面从源码角度详细解析其设计。


1. 依赖收集的存储结构 (targetMap)

核心结构关系

// 核心类型定义(简化版)
type Target = object; // 被代理的原始对象
type Dep = Set<ReactiveEffect>; // 依赖集合(存放副作用函数)
type Key = string | symbol; // 对象的 key(属性名)
type DepsMap = Map<Key, Dep>; // 每个对象的键对应一个依赖集合// 全局存储核心结构
const targetMap = new WeakMap<Target, DepsMap>();
  • targetMap:
    一个 WeakMap,用于存储所有响应式对象与其依赖(DepsMap)的关联。
    键(Key): 代理的原始对象(Target)。
    值(Value): 该对象的 DepsMap

  • DepsMap:
    一个 普通 Map,用于管理对象每个属性(Key)对应的依赖集合。
    键(Key): 字符串或 Symbol(对象属性名)。
    值(Value): 该属性关联的依赖集合 Dep(存放副作用函数)。

  • Dep:
    一个 Set 集合,存储与当前属性相关的所有副作用函数(ReactiveEffect)。


2. 源码流程解析

关键位置源码

  • 源码文件: packages/reactivity/src/effect.tspackages/reactivity/src/reactive.ts

(1) 依赖收集 (track())

访问响应式属性时,track() 函数会触发依赖收集:

export function track(target: object, key: Key) {if (!activeEffect) return; // 当前无活动的 effect,直接返回// 1. 查找或创建 target 对应的 depsMaplet depsMap = targetMap.get(target);if (!depsMap) {targetMap.set(target, (depsMap = new Map()));}// 2. 查找或创建 key 对应的 deplet dep = depsMap.get(key);if (!dep) {depsMap.set(key, (dep = new Set()));}// 3. 将当前活动的 effect(副作用函数)添加到 dep 中if (!dep.has(activeEffect)) {dep.add(activeEffect);activeEffect.deps.push(dep); // 反向记录 effect 的依赖集合}
}

流程解释:

  1. 通过 targetMap 查找原始对象对应的 depsMap
  2. depsMap 中查找该属性(Key)对应的依赖集合 dep
  3. 将当前活动的副作用函数(activeEffect)添加到 dep 中,并建立双向关联。

(2) 依赖触发 (trigger())

修改响应式属性时,trigger() 函数触发副作用函数的执行:

export function trigger(target: object,key: Key,type: TriggerOpTypes // 操作类型(SET/ADD/DELETE等)
) {const depsMap = targetMap.get(target);if (!depsMap) return;// 1. 获取 key 对应的依赖集合const dep = depsMap.get(key);// 2. 可能触发的其他依赖(如数组的 length 变更,或对象的新增/删除操作)const effects: ReactiveEffect[] = [];if (dep) effects.push(...dep);// 3. 遍历并执行所有相关的 effectfor (const effect of effects) {if (effect.options.scheduler) {effect.options.scheduler(effect); // 触发调度器(如异步更新)} else {effect(); // 直接执行}}
}

执行过程:

  1. 根据原始对象和属性名查找 dep
  2. 收集所有关联的 effect
  3. 通过 scheduler 进行调度(例如 Vue3 的 异步批处理更新)。

3. 数据结构设计优势

  1. WeakMap 管理原始对象到依赖的映射:

    • 避免内存泄漏,当原始对象不再被引用时,targetMap 中的条目会自动被 GC 回收。
  2. Map 记录属性到依赖集合的关系:

    • 快速通过属性名(Key)查找到对应的 dep,无需遍历。
  3. Set 存储副作用函数(ReactiveEffect):

    • 自动去重,避免同一个副作用函数被重复收集。

4. 与副作用函数 (ReactiveEffect) 的关联

每个副作用函数(ReactiveEffect)会被动态绑定到当前活动的 activeEffect,并通过 track() 收集到对应的 dep 中。

ReactiveEffect 结构:

class ReactiveEffect {deps: Dep[] = []; // 记录该 effect 被哪些 dep 收集(用于清除依赖)constructor(public fn: () => void, // 副作用函数本体public options?: ReactiveEffectOptions) {}run() {activeEffect = this; // 标记为当前活动的 effectreturn this.fn();}
}

关键操作

  • 执行 effect.run() 时,activeEffect 指向当前 effect。
  • 副作用函数执行过程中访问响应式数据,触发 track()activeEffect 收集到 dep 中。
  • 当响应式数据变化时,通过 dep 找到所有关联的 effect 并重新执行。

5. 示例工作流

假设有以下场景:

const obj = reactive({ count: 0 });
effect(() => {console.log(obj.count); // 访问属性,触发 track
});
obj.count++; // 修改属性,触发 trigger

代码的存储结构变化

  1. reactive(obj) 被 Proxy 包装,访问 obj.count 时会触发 track(target, 'count')
  2. 创建结构
    targetMap: WeakMap{{ count: 0 } => Map{'count' => Set[ effect ]}
    }
    
  3. obj.count++ 修改时触发 trigger(target, 'count'),从 Set 中找到 effect 并执行。

总结

Vue3 的依赖收集通过 WeakMap → Map → Set 的层级结构高效管理对象、属性和副作用函数的关系:

  • WeakMap:响应式对象 → 依赖映射,弱引用避免内存泄漏。
  • Map:对象属性 → 依赖集合,快速通过 Key 查找。
  • Set:存储唯一副作用函数,支持高效的增删查操作。

这一设计在性能与内存管理之间取得了平衡,是 Vue3 响应式系统的核心基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/880762.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

本地部署 DeepSeek-R1-内部知识库

本地部署 DeepSeek-R1-内部知识库本地部署 DeepSeek-R1-内部知识库环境准备1. 确认系统要求2. 安装 Homebrew一. 安装ollama和启动服务1.1 安装 ollama1.2 启动服务1.3 拉取模型并运行二.运行DeepSeek-R1-集成web页面2.1 运行模型2.2 安装Openweb-UI 界面环境准备 1. 确认系统要…

vscode编写javascript代码时ctrl和f12等快捷键失效

提供一种可能性,由于vscode内置扩展@builtin typescript-language-features被关闭导致无法上下文感知跳转。将其重新启用即可

allegro每次保存时都说提示覆盖原图

保存文件时提示,一般新装的软件才会有这个提示 我们改下设置就不会再弹出这个提示了 Setuo-User Preferences,然后在工具栏搜索Savedb点击搜素,如下图复选框里打勾就行

车流量检测摄像头

车流量检测摄像头广泛应用于城市交通管理、高速公路收费站、停车场管理及大型活动现场等多个领域。在城市中,这些设备为交通信号控制提供数据支持,从而提升通行效率。在高速公路上,它们则用于实时监控车速与流量,以便及时调整收费政策和疏导措施。此外,在停车场中,这类设…

安全帽佩戴智能识别摄像机

安全帽佩戴智能识别摄像机的出现,为安全管理带来了革新性的变化。这种智能摄像机基于先进的计算机视觉技术。其核心算法能够精准地对画面中的物体和人员进行识别。在识别安全帽佩戴情况时,它可以迅速分析图像中每个人员头部的特征,准确判断安全帽是否存在。它像是一位不知疲…

使用 CloudDM 和钉钉流程化管理数据库变更审批

CloudDM 是一个专为团队协同工作打造的数据库数据管控平台。在管控数据库安全变更的过程中,为提高效率,CloudDM 接入了钉钉,支持实时通知与移动办公,满足广大企业用户的实际需求。 本文将介绍如何使用 CloudDM 和钉钉实现高效的数据库变更审批。 接入钉钉 创建钉钉应用登录…

tomcat中部署finereport11.0

手动将finereport11.0部署到Tomcat中1、本文目的 在tomcat中直接访问FineReport的cpt文件,跳过登录。 2、执行步骤创建工作目录将%Tomcat_HOME%\webapps\examples文件复制一份出来,重命名为FR,只保留WEB-INF文件夹,其余全部删除。修改web.xml文件编辑%Tomcat_HOME%/webapps…

富滇银行研发管理从数字化走向智能化 | 通义灵码企业标杆案例

经过几个月的持续优化及通义灵码产品能力的迭代,行内用户活跃度持续提升,AI代码生成占比超过30%,单元测试覆盖率持续提升,周期内未发生重大故障。技术人员已养成使用通义灵码进行编码和研发问答的习惯,进一步推动了软件开发的智能化转型。百年传承,富民兴滇。作为一家拥有…

车间产线作业流程合规检测系统

车间产线作业流程合规检测系统通过在车间内安装多个高清摄像头,车间产线作业流程合规检测系统实时捕捉工人在产线上的操作行为。系统可以自动检测工人在生产过程中是否按照正确的顺序执行任务,是否使用正确的工具,是否遵守安全规定等。如果系统检测到工人的操作行为违规,它…

SQL注入-Windows/Docker 环境部署 SQLi-labs 靶场

一、SQLi-labs简介 SQLi-labs是由印度程序员开发,专门用于练习SQL注入的靶场,其中包含各种注入姿势,并适用于GET和POST等场景。 主要包含:基础错误注入、报错注入、盲注、MySQL读写文件、更新查询注入、插入查询注入、Header头部注入、二次注入、绕过WAF、绕过addslashes函…

openai-whisper+fastapi实现语音转文本

Whisper 是一种通用的语音识别模型。它基于各种音频的大型数据集进行训练,也是一种多任务模型,可以执行多语言语音识别、语音翻译和语言识别。Transformer 序列到序列模型针对各种语音处理任务进行训练,包括多语言语音识别、语音翻译、口语识别和语音活动检测。这些任务共同…

在阿里云ECS上一键部署DeepSeek-R1

DeepSeek-R1 是一款开源模型,也提供了 API(接口)调用方式。据 DeepSeek介绍,DeepSeek-R1 后训练阶段大规模使用了强化学习技术,在只有极少标注数据的情况下提升了模型推理能力,该模型性能对标 OpenAl o1 正式版。DeepSeek-R1 推出后,该模型热度持续攀升。 部署流程点击 部…