《Vue.js》阅读之响应式数据与副作用函数
Vue.js
《Vue.js设计与实现》(霍春阳)
- 适合:从零手写Vue3响应式系统,大厂面试源码题直接覆盖。
- 重点章节:第4章(响应式)、第5章(渲染器)、第8章(编译器)
因为我第一周的任务就是响应式原理(Proxy
vs defineProperty
),手写简易reactive
,所以我是从第四章开始学习的。
4.1 响应式数据与副作用函数
1、副作用函数
函数的执行会直接或间接影响其他函数的执行
比如:
01 function effect() {
02 document.body.innerText = 'hello vue3'
03 }
它的执行会使整个body的值都为hello vue3
2、响应式数据
01 const obj = { text: 'hello world' }
02 function effect() {
03 // effect 函数的执行会读取 obj.text
04 document.body.innerText = obj.text
05 }
如上面的这一段代码,我们希望当text发生变化的时候,effect会自动执行,这就是所谓的响应式数据
下面我们将会讲到这是怎么实现的:
● 当副作用函数 effect 执行时,会触发字段obj.text 的读取操作;
● 当修改 obj.text 的值时,会触发字段 obj.text的设置操作。
实现一个响应式的数据:拦截读和写两个步骤,
读的时候把effect函数放在一个容器里面
修改的时候就把这个effect函数释放出来
拦截一个对象的读取和设置操作:Proxy
const data = { text: 'hello' }; // 定义数据对象const bucket = new Set(); // 设置一个容器用于存储副作用函数const obj = new Proxy(data, {// 读的时候拦截get(target, key) {bucket.add(effect); // 将当前活跃的副作用函数添加到容器return target[key]; // 返回属性值},// 拦截设置操作set(target, key, newVal) {target[key] = newVal;bucket.forEach(fn => fn()); // 执行所有存储的副作用函数return true;}});const effect = () => {document.body.innerText = obj.text; // 副作用函数依赖于响应式数据};effect();setTimeout(() => {obj.text = "world"; // 修改数据,触发响应式更新}, 1000); // 延迟1秒修改数据
继续强化->我们现在硬编码了effect,但是如果副作用函数的名字不叫effect的话,这段代码就无法继续工作了
所以我们要提供一个用来注册副作用函数的机制
// 用一个全局变量存储被注册的副作用函数let activeEffect;function effect (fn) {activeEffect = fn ; fn();}const bucket = new Set(); //设置一个容器const data = { text: 'world' }; // 初始化数据对象const obj = new Proxy(data , {//读的时候拦截放在容器里面get( tartget, key ){if(activeEffect){bucket.add(activeEffect);}return tartget[key];//返回属性值},set(target , key , newVal){// 拦截设置操作target[key] = newVal;bucket.forEach(fn => fn());return true;}})effect(() => {console.log('run');document.body.innerText = obj.text;})setTimeout(() => {obj.text = "hello"} , 1000)
当我们为obj添加新的属性的时候
setTimeout(() => {obj.notExist = "hello vue3"} , 1000)
匿名副作用函数内没有读取这个新的属性的值,那么在1s之后不会起到写操作(即放出桶里面的所有函数),但是我尝试了一下,发现它执行了的。
我们为了解决这个问题,就要重新设计“桶”这个数据结构:让它无论读取的哪一个属性都会将副作用函数收到桶里面,设置属性的时候,无论设置的是哪一个属性,也都会将副作用函数取出并执行。
let activeEffect;function effect (fn) {activeEffect = fn; fn();}const bucket = new WeakMap();const data = { text: 'world' }; // 确保所有属性都已定义const obj = new Proxy(data, {get(target, key){if(!activeEffect){return target[key];}// 根据tartget取来的depsMap,它是一个map类型let depsMap = bucket.get(target);// 如果不存在if(!depsMap){// 创建一个bucket.set(target, (depsMap = new Map()));}// 根据key取来的deps,它是一个set类型let deps = depsMap.get(key);// 如果不存在if(!deps){// 创建一个depsMap.set(key, (deps = new Set()));}deps.add(activeEffect); // 添加当前活跃的副作用函数return target[key];},set(target, key, newVal){target[key] = newVal;const depsMap = bucket.get(target);if(!depsMap){return;}const effects = depsMap.get(key);effects && effects.forEach(fn => fn()); // 只触发与键相关的副作用函数}});effect(() => {console.log('run');document.body.innerText = obj.text;});setTimeout(() => {obj.text = "hello vue3"; // 修改已定义的属性以触发依赖}, 1000);
大家可以发现,我们引用了weakMap(它与map最大的不同就是它对key是弱引用,不影响垃圾回收器的工作,通常存储只有当key所引用的对象存在时,才有价值的信息);
我们要解决的是属性不存在时候的问题,那么
- 在读的时候判断是否有这个属性,没有就创建一个,有的话就把函数放在桶里面。
- 在修改的时候也是要判断
最后,我们将一些函数进行封装:
<script setup>let activeEffect;function effect (fn) {activeEffect = fn; fn();}const bucket = new WeakMap();const data = { text: 'world' }; // 确保所有属性都已定义const obj = new Proxy(data, {get(target, key){track(target , key);return target[key];},set(target, key, newVal){target[key] = newVal;trigger(target , key , newVal);}});// 追踪变化function track(target , key){if(!activeEffect){return target[key];}// 根据tartget取来的depsMap,它是一个map类型let depsMap = bucket.get(target);// 如果不存在if(!depsMap){// 创建一个bucket.set(target, (depsMap = new Map()));}// 根据key取来的deps,它是一个set类型let deps = depsMap.get(key);// 如果不存在if(!deps){// 创建一个depsMap.set(key, (deps = new Set()));}deps.add(activeEffect); // 添加当前活跃的副作用函数}// 触发变化function trigger(target , key , newVal){const depsMap = bucket.get(target);if(!depsMap){return;}const effects = depsMap.get(key);effects && effects.forEach(fn => fn()); // 只触发与键相关的副作用函数}effect(() => {console.log('run');document.body.innerText = obj.text;});setTimeout(() => {obj.text = "hello vue3"; // 修改已定义的属性以触发依赖}, 1000);
</script>
相关文章:
《Vue.js》阅读之响应式数据与副作用函数
Vue.js 《Vue.js设计与实现》(霍春阳) 适合:从零手写Vue3响应式系统,大厂面试源码题直接覆盖。重点章节:第4章(响应式)、第5章(渲染器)、第8章(编译器&…...

如何应对网站被爬虫和采集?综合防护策略与实用方案
在互联网时代,网站内容被恶意爬虫或采集工具窃取已成为常见问题。这不仅侵犯原创权益,还可能影响网站性能和SEO排名。以下是结合技术、策略与法律的综合解决方案,帮助网站构建有效防护体系。 一、技术防护:阻断爬虫的“技术防线”…...

AI智慧公园管理方案:用科技重塑市民的“夜游体验”
AI智慧公园管理方案:多场景智能巡检与安全防控 一、背景与痛点分析 夏季夜间,公园成为市民休闲娱乐的核心场所,但管理难度随之激增: 宠物管理失控:未牵绳宠物进入园区,随地排泄、惊扰游客,甚…...
学习日志04 java
PTA上的练习复盘 java01 编程题作业感悟: 可以用ai指导自己怎么调试,但是不要把调代码这过程里面的精华交给ai,就是自己去修正错误不能让ai代劳!~~~ 1 scanner.close() Scanner *** new Scanner(System.in); ***.close(); …...

LVGL- 按钮矩阵控件
1 按钮矩阵控件 lv_btnmatrix 是 LVGL(Light and Versatile Graphics Library) v8 中提供的一个非常实用的控件,用于创建带有多个按钮的矩阵布局。它常用于实现虚拟键盘、数字键盘、操作面板、选择菜单等场景,特别适用于嵌入式设…...
【node】6 包与npm
前言 目标 1 了解什么是包 2 怎么使用npm下载包 #mermaid-svg-Ur0d2uCdQeAQOJjW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Ur0d2uCdQeAQOJjW .error-icon{fill:#552222;}#mermaid-svg-Ur0d2uCdQeAQOJjW .erro…...

1. 使用 IntelliJ IDEA 创建 React 项目:创建 React 项目界面详解;配置 Yarn 为包管理器
1. 使用 IntelliJ IDEA 创建 React 项目:创建 React 项目界面详解;配置 Yarn 为包管理器 🧩 使用 IntelliJ IDEA 创建 React 项目(附 Yarn 配置与 Vite 建议)📷 创建 React 项目界面详解1️⃣ Name…...
T-SQL在SQL Server中判断表、字段、索引、视图、触发器、Synonym等是否存在
SQL Server创建或者删除表、字段、索引、视图、触发器前判断是否存在。 目录 1. SQL Server创建表之前判断表是否存在 2. SQL Server新增字段之前判断是否存在 3. SQL Server删除字段之前判断是否存在 4. SQL Server新增索引之前判断是否存在 5. SQL Server判断视图是否存…...
Docker中mysql镜像保存与导入
一、Docker中mysql镜像保存 Docker 的 MySQL 镜像保存通常有两种场景:一种是保存镜像本身的修改(如配置、初始化数据),另一种是持久化保存容器运行时产生的数据(如数据库表、用户数据)。以下是具体方法&am…...

【JVM】从零开始深度解析JVM
本篇博客给大家带来的是JVM的知识点, 重点在类加载和垃圾回收机制上. 🐎文章专栏: JavaEE初阶 🚀若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅🚀 …...

算法训练营第十四天|110. 平衡二叉树、257. 二叉树的所有路径、404. 左叶子之和、222.完全二叉树的节点个数
110.平衡二叉树 题目 思路与解法 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution:def isBalanced(self, r…...
Java单例模式总结
说明:单例模式的核心是确保一个类只有一个实例,并提供全局访问点。饿汉式和懒汉式是两种常见的实现方式 一、饿汉式和懒汉式 1. 饿汉式(Eager Initialization) public class EagerSingleton {// 类加载时直接初始化实例private…...

在 Elasticsearch 中删除文档中的某个字段
作者:来自 Elastic Kofi Bartlett 探索在 Elasticsearch 中删除文档字段的方法。 更多有关 Elasticsearch 文档的操作,请详细阅读文章 “开始使用 Elasticsearch (1)”。 想获得 Elastic 认证?查看下一期 Elasticsear…...
OpenCV中适用华为昇腾(Ascend)后端的逐元素操作(Per-element Operations)
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 针对华为昇腾(Ascend)后端的逐元素操作(Per-element Operations),这些操作通常用于图…...
Jenkins集成Maven
一、概述 Jenkins是一个开源的持续集成工具,用于自动化各种开发任务。Maven是一个项目管理和构建自动化工具,主要用于Java项目。通过将Jenkins和Maven集成,可以实现自动化构建、测试和部署,提高开发效率和代码质量。 二、前提条…...

初识Linux · TCP基本使用 · 回显服务器
目录 前言: 回显服务器 TCPserver_v0 TCPserver_v1--多进程版本 TCPserver_v2--多线程版本 前言: 前文我们介绍了UDP的基本使用,本文我们介绍TCP的基本使用,不过TCP的使用我们这里先做一个预热,即只是使用TCP的A…...
Qwen:Qwen3,R1 在 Text2SQL 效果评估
【对比模型】 Qwen3 235B-A22B(2350亿总参数,220亿激活参数),32B,30B-A3B;QwQ 32B(推理模型)DeepSeek-R1 671B(满血版)(推理模型) 1&a…...

【layout组件 与 路由镶嵌】vue3 后台管理系统
前言 很多同学在第一次搭建后台管理系统时,会遇到一个问题,layout组件该放哪里?如何使用?路由又该如何设计? 这边会讲一下我的思考过程和最后的结果,大家可以参考一下,希望大家看完能有所收获。…...

mobile自动化测试-appium webdriverio
WebdriverIO是一款支持mobile app和mobile web自动化测试框架,与appium集成,完成对mobile应用测试。支持ios 和android两种平台,且功能丰富,是mobile app自动化测试首选框架。且官方还提供了mobile 应用测试example代码࿰…...

Spring Bean有哪几种配置方式?
大家好,我是锋哥。今天分享关于【Spring Bean有哪几种配置方式?】面试题。希望对大家有帮助; Spring Bean有哪几种配置方式? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Bean的配置方式主要有三种ÿ…...

解析小米大模型MiMo:解锁语言模型推理潜力
一、基本介绍 1.1 项目背景 在大型语言模型快速发展的背景下,小米AI团队推出MiMo系列模型,突破性地在7B参数规模上实现卓越推理能力。传统观点认为32B以上模型才能胜任复杂推理任务,而MiMo通过创新的训练范式证明:精心设计的预训练和强化学习策略,可使小模型迸发巨大推理…...

证券行业数字化转型:灵雀云架设云原生“数字高速路”
01 传统架构难承重负,云原生破局成必然 截至2024年,证券行业总资产突破35万亿元,线上交易占比达85%,高频交易、智能投顾等业务对算力与响应速度提出极限要求。然而,以虚拟化为主导的传统IT架构面临四大核心瓶颈&#…...

Centos系统详解架构详解
CentOS 全面详解 一、CentOS 概述 CentOS(Community Enterprise Operating System) 是基于 Red Hat Enterprise Linux(RHEL) 源代码构建的免费开源操作系统,专注于稳定性、安全性和长期支持,广泛应用于服…...

【后端】SpringBoot用CORS解决无法跨域访问的问题
SpringBoot用CORS解决无法跨域访问的问题 一、跨域问题 跨域问题指的是不同站点之间,使用 ajax 无法相互调用的问题。跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。但这个保护机制也带来了新的…...

MySQL 8.0(主从复制)
MySQL 8.0 的 主从复制(Master-Slave Replication) 是一种数据库高可用和数据备份的核心技术,下面用 一、什么是主从复制? 就像公司的「领导-秘书」分工: 主库(Master):负责处理所…...

TCPIP详解 卷1协议 十 用户数据报协议和IP分片
10.1——用户数据报协议和 IP 分片 UDP是一种保留消息边界的简单的面向数据报的传输层协议。它不提供差错纠正、队列管理、重复消除、流量控制和拥塞控制。它提供差错检测,包含我们在传输层中碰到的第一个真实的端到端(end-to-end)校验和。这…...

finebi使用资源迁移无法导入资源,解决方法
finebi使用资源迁移无法导入资源,解决方法 最近在使用finebi开发finebi报表,报表开发之后,从一台电脑将资源导入另一台电脑后,出现不允许导入的提示,如下: 原因: 两个finebi的管理员名称不一致…...
【ASR学习笔记】:语音识别领域基本术语
一、基础术语 ASR (Automatic Speech Recognition) 自动语音识别,把语音信号转换成文本的技术。 VAD (Voice Activity Detection) 语音活动检测,判断一段音频里哪里是说话,哪里是静音或噪音。 Acoustic Model(声学模型࿰…...
精益数据分析(53/126):双边市场模式指标全解析与运营策略深度探讨
精益数据分析(53/126):双边市场模式指标全解析与运营策略深度探讨 在创业与数据分析的探索之路上,深入了解各类商业模式的关键指标和运营策略至关重要。今天,我们依然怀揣着与大家共同进步的信念,深入研读…...

分布式锁redisson的中断操作
1、先贴代码 RequestMapping(value "/update", method RequestMethod.POST)ResponseBodypublic Result update(RequestBody Employee employee) { // 修改数据库(存在线程不安全 需要使用redison设置分布式锁 防止被修改) // 设…...