深挖vue3基本原理之一 —— 响应式系统(Reactivity System)

news/2025/2/13 18:25:15/

响应式系统(Reactivity System)

1.1 基于 Proxy 的响应式代理

在 Vue 3 中,响应式系统的核心是使用 ES6 的 Proxy 来替代 Vue 2 里的 Object.defineProperty 方法,以此实现更加全面和强大的响应式追踪功能。下面我们来详细剖析这个过程。

响应式代理的实现代码
javascript">const reactive = (target) => {return new Proxy(target, {get(target, key, receiver) {track(target, key) // 依赖收集return Reflect.get(target, key, receiver)},set(target, key, value, receiver) {Reflect.set(target, key, value, receiver)trigger(target, key) // 触发更新return true}})
}
代码详细解释
  • reactive 函数:该函数接收一个目标对象 target 作为参数,返回一个使用 Proxy 代理后的新对象。Proxy 是 ES6 提供的一个强大特性,它可以拦截并重新定义对象的基本操作,比如属性的读取和设置。
  • get 拦截器:当访问代理对象的属性时,会触发 get 拦截器。
    • track(target, key):调用 track 函数进行依赖收集。依赖收集的目的是记录哪些副作用函数依赖于当前访问的属性,以便在属性值发生变化时能够通知这些副作用函数进行更新。
    • Reflect.get(target, key, receiver):使用 Reflect.get 方法获取目标对象的属性值。Reflect 是 ES6 新增的一个内置对象,它提供了一系列用于操作对象的方法,与 Proxy 配合使用可以更方便地实现对象的拦截操作。
  • set 拦截器:当设置代理对象的属性时,会触发 set 拦截器。
    • Reflect.set(target, key, value, receiver):使用 Reflect.set 方法设置目标对象的属性值。
    • trigger(target, key):调用 trigger 函数触发更新。当属性值发生变化时,需要通知所有依赖该属性的副作用函数重新执行。
相比 Object.defineProperty 的优势
  • 支持数组索引修改和 length 变化检测:在 Vue 2 中,使用 Object.defineProperty 来实现响应式时,对于数组的一些操作(如通过索引修改元素、修改 length 属性)无法直接检测到变化。而在 Vue 3 中,使用 Proxy 可以轻松地拦截这些操作,从而实现对数组的全面响应式追踪。
javascript">const arr = reactive([1, 2, 3]);
arr[0] = 10; // 可以正常触发更新
arr.length = 2; // 也可以正常触发更新
  • 自动追踪新增对象属性(无需 Vue.set:在 Vue 2 中,如果要给响应式对象新增一个属性,需要使用 Vue.set 方法才能让新增的属性也具有响应式特性。而在 Vue 3 中,由于使用了 Proxy,可以自动追踪对象新增的属性,无需额外的操作。
javascript">const obj = reactive({ a: 1 });
obj.b = 2; // 新增属性 b 会自动具有响应式特性
  • 深度监听嵌套对象(Lazy 模式,按需激活):Vue 3 的响应式系统会对嵌套对象进行深度监听,但采用的是 Lazy 模式,即只有在访问嵌套对象的属性时才会激活对该嵌套对象的响应式追踪,这样可以提高性能。
javascript">const nestedObj = reactive({inner: {c: 3}
});
// 当访问 nestedObj.inner.c 时,才会激活对 inner 对象的响应式追踪
1.2 依赖管理机制

Vue 3 的响应式系统采用了三层依赖管理体系,通过 WeakMapMapSet 来高效地管理依赖关系。

三层依赖管理体系的结构
  • TargetMap:是一个 WeakMap,用于存储目标对象到键的映射。WeakMap 的键必须是对象,并且这些对象是弱引用,即如果对象没有其他引用指向它,它可以被垃圾回收,这样可以避免内存泄漏。
  • DepsMap:是一个 Map,用于存储键到依赖集合的映射。每个键对应一个依赖集合,该集合存储了所有依赖于该键的副作用函数。
  • Dep:是一个 Set,用于存储具体的副作用函数。Set 是一种无序且唯一的数据结构,确保每个副作用函数只被存储一次。
依赖管理的代码实现
type Dep = Set<ReactiveEffect>;
type KeyToDepMap = Map<any, Dep>;
const targetMap = new WeakMap<any, KeyToDepMap>();function track(target: object, key: unknown) {if (!activeEffect) return;let depsMap = targetMap.get(target);if (!depsMap) {targetMap.set(target, (depsMap = new Map()));}let dep = depsMap.get(key);if (!dep) {depsMap.set(key, (dep = new Set()));}dep.add(activeEffect);
}
代码详细解释
  • 类型定义
    • Dep:定义为 Set<ReactiveEffect>,表示一个存储副作用函数的集合。
    • KeyToDepMap:定义为 Map<any, Dep>,表示一个存储键到依赖集合的映射。
  • targetMap:是一个全局的 WeakMap,用于存储所有目标对象的依赖信息。
  • track 函数:用于进行依赖收集。
    • if (!activeEffect) return;:如果当前没有活跃的副作用函数,则直接返回,不进行依赖收集。
    • let depsMap = targetMap.get(target);:从 targetMap 中获取目标对象对应的 DepsMap
    • if (!depsMap) { targetMap.set(target, (depsMap = new Map())); }:如果 DepsMap 不存在,则创建一个新的 Map 并存储到 targetMap 中。
    • let dep = depsMap.get(key);:从 DepsMap 中获取键对应的依赖集合。
    • if (!dep) { depsMap.set(key, (dep = new Set())); }:如果依赖集合不存在,则创建一个新的 Set 并存储到 DepsMap 中。
    • dep.add(activeEffect);:将当前活跃的副作用函数添加到依赖集合中。
1.3 副作用调度

Vue 3 的响应式系统基于调度器实现了异步更新队列,以提高性能和避免不必要的重复更新。

异步更新队列的实现代码
javascript">const queue = new Set();
let isFlushing = false;function queueJob(job) {queue.add(job);if (!isFlushing) {isFlushing = true;Promise.resolve().then(() => {try {queue.forEach(job => job());} finally {queue.clear();isFlushing = false;}});}
}
代码详细解释
  • queue:是一个 Set,用于存储需要执行的副作用函数。使用 Set 可以确保每个副作用函数只被存储一次,避免重复执行。
  • isFlushing:是一个布尔值,用于标记当前是否正在执行更新队列中的副作用函数。
  • queueJob 函数:用于将副作用函数添加到更新队列中。
    • queue.add(job):将副作用函数添加到 queue 中。
    • if (!isFlushing) { ... }:如果当前没有正在执行更新队列中的副作用函数,则启动异步更新。
      • isFlushing = true;:标记为正在执行更新。
      • Promise.resolve().then(() => { ... }):使用 Promise 实现异步执行。在微任务队列中执行更新队列中的副作用函数。
      • try { queue.forEach(job => job()); } finally { queue.clear(); isFlushing = false; }:遍历更新队列,执行每个副作用函数。执行完毕后,清空队列并将 isFlushing 标记为 false,表示更新完成。

通过这种异步更新队列的方式,Vue 3 可以将多次属性变化引起的副作用函数执行合并为一次,从而提高性能。例如,在短时间内多次修改响应式对象的属性,只会触发一次副作用函数的执行。

综上所述,Vue 3 的响应式系统通过基于 Proxy 的响应式代理、三层依赖管理机制和副作用调度,实现了高效、全面的响应式追踪和更新功能。


http://www.ppmy.cn/news/1571772.html

相关文章

Centos10 Stream 基础配置

NetworkManger 安装 dnf install NetworkManager 查看网络配置 nmcli [rootCentos-S-10 /]# nmcli ens33&#xff1a;已连接 到 ens33"Intel 82545EM"ethernet (e1000), 00:0C:29:08:3E:71, 硬件, mtu 1500ip4 默认inet4 192.168.31.70/24route4 default …

Leetcode - 周赛435

目录 一、3442. 奇偶频次间的最大差值 I二、3443. K 次修改后的最大曼哈顿距离三、3444. 使数组包含目标值倍数的最少增量四、3445. 奇偶频次间的最大差值 II 一、3442. 奇偶频次间的最大差值 I 题目链接 本题使用数组统计字符串 s s s 中每个字符的出现次数&#xff0c;然后…

spring cloud和spring boot的区别

Spring Cloud和Spring Boot在Java开发领域中都是非常重要的框架&#xff0c;但它们在目标、用途和实现方式上存在明显的区别。以下是对两者区别的详细解析&#xff1a; 1. 含义与定位 Spring Boot&#xff1a; 是一个快速开发框架&#xff0c;它简化了Spring应用的初始搭建以…

前端性能分析常见内容

前端性能分析是前端开发中的重要部分&#xff0c;以下是对前端常考性能分析题目的详解&#xff1a; 一、性能指标 前端性能优化的核心目标是提升用户体验&#xff0c;常见的性能指标包括&#xff1a; 加载时间&#xff08;Load Time&#xff09;&#xff1a;指从用户发出请求…

LabVIEW用户界面设计原则

在LabVIEW开发中&#xff0c;用户界面&#xff08;UI&#xff09;设计不仅仅是为了美观&#xff0c;它直接关系到用户的操作效率和体验。一个直观、简洁、易于使用的界面能够大大提升软件的可用性&#xff0c;尤其是在复杂的实验或工业应用中。设计良好的UI能够减少操作错误&am…

MindStudio制作MindSpore TBE算子(四)算子测试(ST测试-Ascend910B/ModelArts)--失败尝试

上一节&#xff0c;MindStudio制作MindSpore TBE算子&#xff08;三&#xff09;算子测试&#xff08;ST测试&#xff09;&#xff0c;因此缺乏对应的硬件环境导致无法进行ST测试&#xff0c;导致难以自安&#xff0c;今天搞来Ascend910B服务器来填坑&#xff0c;看看是否是硬件…

GitHub 使用教程:从入门到进阶

1. GitHub账号注册 访问 GitHub 官网 (https://github.com)点击 “Sign up” 按钮填写用户名、邮箱和密码验证邮箱完成注册 2. 基础配置 2.1 安装Git 访问 Git 官网下载安装包运行安装程序&#xff0c;按提示完成安装打开终端&#xff0c;设置用户信息&#xff1a; git co…

如何获取,CPU,GPU,硬盘,网卡,内存等硬件性能监控与各项温度传感器

首先需要下载 OpenHardwareMonitorServer 这是一个基于OpenHardwareMonitor 的 Web 服务器。可以让任何语言都可以获取硬件信息和值&#xff0c;OpenHardwareMonitorServer 是没有UI界面的因此它可以当成控制台程序使用。 该程序可用参数如下 参数&#xff1a;需要管理员权限…