当前位置: 首页 > news >正文

《Vue进阶教程》第十六课:深入完善响应式系统之单例模式

   往期内容:

《Vue进阶教程》第五课:ref()函数详解(重点)

《Vue进阶教程》第六课:computed()函数详解(上)

《Vue进阶教程》第七课:computed()函数详解(下)

《Vue进阶教程》第八课:watch()函数的基本使用

《Vue进阶教程》第九课:watch()函数的高级使用

《Vue进阶教程》第十课:其它函数

《Vue进阶教程》第十一课:响应式系统介绍

《Vue进阶教程》第十二课:实现一对多

《Vue进阶教程》第十三课:实现依赖收集

《Vue进阶教程》第十四课:改进桶结构

《Vue进阶教程》第十五课:深入完善响应式系统之模块化

🤔思考

  1. 对于同一个源对象每次调用reactive返回的代理对象应该是一样的
  2. 对于一个已经代理过的对象再次代理应该返回的也应该是一样的

1) 实现单例

为了实现单例, 我们需要建立源对象->代理对象的映射关系

  • 如果存在映射, 说明已经代理过了, 直接返回
  • 如果不存在映射, 说明没有代理过, 创建一个新的代理对象返回

定义源对象->代理对象的映射表(使用WeakMap)

reactive.js

// 定义一个副作用函数桶, 存放所有的副作用函数. 每个元素都是一个副作用函数
// 修改 [state -> Map[name: Set(fn, fn), age: Set(fn, fn)], state1 -> Map]
const bucket = new WeakMap()// 建立一个映射表 target -> proxy
const reactiveMap = new WeakMap() // 新增// 定义一个全局变量, 保存当前正在执行的副作用函数
let activeEffect = nullfunction isObject(value) {return typeof value === 'object' && value !== null
}// 收集依赖
function track(target, key) {// 只有activeEffect有值时(保存的副作用函数), 才添加到桶中if (!activeEffect) returnlet depMap = bucket.get(target)if (!depMap) {depMap = new Map()bucket.set(target, depMap)}let depSet = depMap.get(key)if (!depSet) {depSet = new Set()depMap.set(key, depSet)}depSet.add(activeEffect)
}function trigger(target, key) {let depMap = bucket.get(target)if (!depMap) return// 从副作用函数桶中依次取出每一个元素(副作用函数)执行let depSet = depMap.get(key)if (depSet) {depSet.forEach((fn) => fn())}
}
/*** 创建响应式数据*  @param [object]: 普通对象*  @return [Proxy]: 代理对象*/
function reactive(data) {if (!isObject(data)) return// 如果映射表中存在了对应关系if (reactiveMap.has(data)) {// 返回data对应的代理对象return reactiveMap.get(data)}const proxy = new Proxy(data, {get(target, key) {// 在get操作时, 收集依赖track(target, key)return target[key]},set(target, key, value) {target[key] = value// 在set操作时, 触发副作用重新执行trigger(target, key)return true},})// 建立data(源对象)和proxy(代理对象)的映射关系reactiveMap.set(data, proxy)return proxy
}/*** 注册副作用函数*  @param [function]: 需要注册的 副作用函数*/
function effect(fn) {if (typeof fn !== 'function') return// 记录正在执行的副作用函数activeEffect = fn// 调用副作用函数fn()// 重置全局变量activeEffect = null
}

 测试用例

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="./reactive.js"></script></head><body><script>const source = { name: 'hello' }const p = reactive(source)const p1 = reactive(source)console.log(p === p1) // true</script></body>
</html>

2) 实现重复代理

可以定义一个特殊的标识__v_isReactive

  • 如果存在该标识, 说明已经代理过, 直接返回
  • 如果不存在该标识, 说明没有被代理, 创建新的代理对象

示例

// 定义源对象->代理对象映射表
const reactiveMap = new WeakMap()// 定义一个副作用桶bucket
const bucket = new WeakMap() 
// 定义一个全局变量, 作于保存 `当前副作用函数`
let activeEffect = null// 收集依赖
function track(target, key) {// 根据不同的target, 获取对应的Maplet depMap = bucket.get(target)if (!depMap) {depMap = new Map()bucket.set(target, depMap)}let depSet = depMap.get(key)if (!depSet) {depSet = new Set()depMap.set(key, depSet)}depSet.add(activeEffect)
}// 触发执行
function trigger(target, key) {let depMap = bucket.get(target)if (!depMap) returnlet depSet = depMap.get(key)if (depSet) {// 如果对应的集合存在, 遍历集合中的每个函数depSet.forEach((fn) => fn())}
}/*** 定义响应式*  @param [object] : 普通对象*  @return [Proxy] : 代理对象*/
export function reactive(data) {// 如果传入的data不是一个普通对象, 不处理if (typeof data !== 'object' || data == null) returnif (reactiveMap.has(data)) {// 返回data对应的代理对象return reactiveMap.get(data)}// 如果存在标识, 说明data被代理过了if (data['__v_isReactive']) {return data}const proxy = new Proxy(data, {get(target, key) {if (key == '__v_isReactive') return true // 新增// console.log(`自定义访问${key}`)if (activeEffect != null) {// 收集依赖track(target, key)}return target[key]},set(target, key, value) {// console.log(`自定义设置${key}=${value}`)target[key] = value // 先更新值// 触发更新trigger(target, key)return true},})reactiveMap.set(data, proxy)return proxy
}/*** 注册副作用函数* @params [function]: 要注册的 副作用函数*/
export function effect(fn) {if (typeof fn !== 'function') return// 将当前注册的副作用函数 保存 到全局变量中activeEffect = fn// 执行当前副作用函数, 收集依赖fn()// 重置全局变量activeEffect = null
}

 测试用例

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="./reactive.js"></script></head><body><script>const source = { name: 'hello' }const p = reactive(source)const p1 = reactive(p)console.log(p === p1) // true</script></body>
</html>

相关文章:

《Vue进阶教程》第十六课:深入完善响应式系统之单例模式

往期内容&#xff1a; 《Vue进阶教程》第五课&#xff1a;ref()函数详解(重点) 《Vue进阶教程》第六课&#xff1a;computed()函数详解(上) 《Vue进阶教程》第七课&#xff1a;computed()函数详解(下) 《Vue进阶教程》第八课&#xff1a;watch()函数的基本使用 《Vue进阶教…...

C语言版解法力扣题:将整数按权重排序

1.题目描述 我们将整数 x 的 权重 定义为按照下述规则将 x 变成 1 所需要的步数&#xff1a; 如果 x 是偶数&#xff0c;那么 x x / 2 如果 x 是奇数&#xff0c;那么 x 3 * x 1 比方说&#xff0c;x3 的权重为 7 。因为 3 需要 7 步变成 1 &#xff08;3 --> 10 -->…...

Unity ECS和OOP优劣对比

OOP的优劣 面向对象编程&#xff08;OOP, Object-Oriented Programming&#xff09;是一种通过对象及其交互来组织代码的编程范式&#xff0c;广泛应用于软件开发中。以下是OOP的优缺点&#xff1a; 优点 代码可重用性 继承机制&#xff1a;通过继承&#xff0c;子类可以复用…...

【Java基础面试题026】Java中的String、StringBuffer和StringBuilder的区别是什么?

回答重点 他们都是Java中处理字符串的类&#xff0c;区别主要体现在可变性、线程安全和性能上 1&#xff09;String 不可变&#xff1a;String是不可变类&#xff0c;字符串对象创建&#xff0c;存储在堆中&#xff0c;字符串内容存储在字符串常量池中&#xff0c;一旦创建内…...

解析在OceanBase创建分区的常见问题|OceanBase 用户问题精粹

在《分区策略和管理分区计划的实践方案》这篇文章中&#xff0c;我们介绍了在ODC中制定分区策略及有效管理分区计划的经验。有不少用户在该帖下提出了使用中的问题&#xff0c;其中一个关于创建分区的限制条件的问题&#xff0c;也是很多用户遭遇的老问题。因此本文以其为切入&…...

Flutter组件————Container

Container Container 是 Flutter 中最常用的布局组件之一 参数 参数名称类型描述alignmentAlignmentGeometry定义子组件在其内部的对齐方式&#xff0c;默认为 null&#xff0c;即不改变子组件的位置。paddingEdgeInsetsGeometry内边距&#xff0c;用于在子组件周围添加空间…...

Java重要面试名词整理(二):SpringMyBatis

文章目录 Spring篇Spring核心推断构造方法AOP动态代理Advice的分类Advisor的理解AOP相关的概念 定义BeanASM技术JFR依赖注入循环依赖LifecycleSpring AOT Spring事务Spring事务传播机制Spring事务传播机制是如何实现的呢?Spring事务传播机制分类 SpringMVCHandlerHandlerMappi…...

Excel生成DBC脚本源文件

Excel制作 新建一个Excel&#xff0c;后缀为“.xls” 工作本名称改为“CAN_Matrix” 在首行按照列来起名字&#xff0c;在里面只需要填写必须的内容即可。 列数名称第0列Message Name第1列Message Format第2列Message ID第3列Message Length (byte)第4列Message Transmitte…...

Git进阶:本地或远程仓库如何回滚到之前的某个commit

在Git的使用过程中&#xff0c;我们经常会遇到需要回滚到之前某个commit的情况。无论是为了修复错误、撤销更改&#xff0c;还是为了重新组织代码&#xff0c;回滚到特定commit都是一个非常有用的技能。本文将介绍几种常用的回滚方法&#xff0c;帮助读者更好地掌握Git版本控制…...

linux 中文输入法设置的宏观思路 (****)

乱是永远的不乱,变是永远的不变。 $ ibus help # 注意:help 前没有杠符号 $ setxkbmap -help # 注意:help 前只有一个杠符号 $ localectl --help # 注意:help 前有 2 个杠符号 $ man im-config # 注意:-h, --help 只出来提示信息:请参考。。。。。。。 $ man abc…...

271-基于XC7V690T的12路光纤PCIe接口卡

一、板卡概述 基于XC7V690T的12路光纤PCI-E接口卡&#xff0c;用于实现多通道高速光纤数据接收和发送&#xff0c;板卡兼容PCIe 2.0和PCIe 3.0规范&#xff0c;利用PCI-E Switch PEX 8748实现FPGA芯片与计算机的通信&#xff0c;计算机与板卡接口处提供PCI-e 16速接口&#xff…...

Semaphore UI安装和实践

本次实验环境采用centos7.9操作系统&#xff0c;使用rpm包安装方式。 1.配置yum源 --下载centos华为云镜像仓库 wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.huaweicloud.com/repository/conf/CentOS-7-anon.repo[rootlocalhost ~]# wget -O /etc/yum.repos.…...

Redis篇--常见问题篇7--缓存一致性2(分布式事务框架Seata)

1、概述 在传统的单体应用中&#xff0c;事务管理相对简单&#xff0c;通常使用数据库的本地事务&#xff08;如MySQL的BEGIN和COMMIT&#xff09;来保证数据的一致性。然而&#xff0c;在微服务架构中&#xff0c;由于每个服务都有自己的数据库&#xff0c;跨服务的事务管理变…...

Docker Compose 安装 Harbor

我使用的系统是rocky Linux 9 1. 准备环境 确保你的系统已经安装了以下工具&#xff1a; DockerDocker ComposeOpenSSL&#xff08;用于生成证书&#xff09;#如果不需要通过https连接的可以不设置 1.1 安装 Docker 如果尚未安装 Docker&#xff0c;可以参考以下命令安装&…...

使用docker compose安装gitlab

使用docker compose安装gitlab GitLab简介设置GITLAB_HOME路径创建docker挂载目录获取可用的GitLab版本编写docker-compose.yml文件启动docker基础配置GITLAB_OMNIBUS_CONFIG修改配置 中文设置数据库配置系统邮箱配置 GitLab简介 ‌GitLab是一个基于Git的开源项目&#xff0c;…...

大模型日报 2024-12-18

大模型日报 2024-12-18 大模型资讯 标题&#xff1a; 3B模型长思考后击败70B&#xff01;HuggingFace逆向出o1背后技术细节并开源 摘要&#xff1a;这篇文章探讨了小模型在经过长时间思考后&#xff0c;如何在性能上超越更大规模模型的现象。HuggingFace通过逆向工程和开源技术…...

Linux安装mysql5.7

一、下载mysql5.7 ​ 首先我们需要去下载linux版本的mysql-5.7.24的安装包。 1.可以去官方网站链接: https://downloads.mysql.com/archives/community/ ,下载mysql-5.7.24-linux-glibc2.12-x86_64.tar压缩包。 2.在线下载&#xff0c;使用wget命令&#xff0c;直接从官网下载…...

【容器】k8s学习笔记原理详解(十万字超详细)

Pod详解 Pod介绍 Pod结构 每个Pod中都可以包含一个或者多个容器&#xff0c;这些容器可以分为两类&#xff1a; 用户程序所在的容器&#xff0c;数量可多可少Pause容器&#xff0c;这是每个Pod都会有的一个根容器&#xff0c;它的作用有两个&#xff1a; 可以以它为依据&am…...

.NET重点

B/S C/S B/S&#xff1a; 浏览器端&#xff1a;JavaScript&#xff0c;HTML&#xff0c;CSS 服务器端&#xff1a;ASP&#xff08;.NET&#xff09;PHP/JSP 优势&#xff1a;维护方便&#xff0c;易于升级和扩展 劣势&#xff1a;服务器负担沉重 C/S java/.NET/VC系列 …...

SMMU软件指南SMMU编程之虚拟机结构和缓存

安全之安全(security)博客目录导读 目录 一、虚拟机结构(VMS) 二、缓存 一、虚拟机结构(VMS) 虚拟机结构(VMS)是SMMU中的概念,是一个由STE.VMSPtr字段指向的结构,包含每个虚拟机的配置设置。在相同安全状态下具有相同虚拟机ID(VMID)的多个STE必须指向相同的VMS。…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

Java求职者面试指南:计算机基础与源码原理深度解析

Java求职者面试指南&#xff1a;计算机基础与源码原理深度解析 第一轮提问&#xff1a;基础概念问题 1. 请解释什么是进程和线程的区别&#xff1f; 面试官&#xff1a;进程是程序的一次执行过程&#xff0c;是系统进行资源分配和调度的基本单位&#xff1b;而线程是进程中的…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing

Muffin 论文 现有方法 CRADLE 和 LEMON&#xff0c;依赖模型推理阶段输出进行差分测试&#xff0c;但在训练阶段是不可行的&#xff0c;因为训练阶段直到最后才有固定输出&#xff0c;中间过程是不断变化的。API 库覆盖低&#xff0c;因为各个 API 都是在各种具体场景下使用。…...

DAY 26 函数专题1

函数定义与参数知识点回顾&#xff1a;1. 函数的定义2. 变量作用域&#xff1a;局部变量和全局变量3. 函数的参数类型&#xff1a;位置参数、默认参数、不定参数4. 传递参数的手段&#xff1a;关键词参数5 题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一…...

相关类相关的可视化图像总结

目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系&#xff0c;可直观判断线性相关、非线性相关或无相关关系&#xff0c;点的分布密…...