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

《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设计与实现》&#xff08;霍春阳&#xff09; 适合&#xff1a;从零手写Vue3响应式系统&#xff0c;大厂面试源码题直接覆盖。重点章节&#xff1a;第4章&#xff08;响应式&#xff09;、第5章&#xff08;渲染器&#xff09;、第8章&#xff08;编译器&…...

如何应对网站被爬虫和采集?综合防护策略与实用方案

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

AI智慧公园管理方案:用科技重塑市民的“夜游体验”

AI智慧公园管理方案&#xff1a;多场景智能巡检与安全防控 一、背景与痛点分析 夏季夜间&#xff0c;公园成为市民休闲娱乐的核心场所&#xff0c;但管理难度随之激增&#xff1a; 宠物管理失控&#xff1a;未牵绳宠物进入园区&#xff0c;随地排泄、惊扰游客&#xff0c;甚…...

学习日志04 java

PTA上的练习复盘 java01 编程题作业感悟&#xff1a; 可以用ai指导自己怎么调试&#xff0c;但是不要把调代码这过程里面的精华交给ai&#xff0c;就是自己去修正错误不能让ai代劳&#xff01;~~~ 1 scanner.close() Scanner *** new Scanner(System.in); ***.close(); …...

LVGL- 按钮矩阵控件

1 按钮矩阵控件 lv_btnmatrix 是 LVGL&#xff08;Light and Versatile Graphics Library&#xff09; v8 中提供的一个非常实用的控件&#xff0c;用于创建带有多个按钮的矩阵布局。它常用于实现虚拟键盘、数字键盘、操作面板、选择菜单等场景&#xff0c;特别适用于嵌入式设…...

【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 项目&#xff1a;创建 React 项目界面详解&#xff1b;配置 Yarn 为包管理器 &#x1f9e9; 使用 IntelliJ IDEA 创建 React 项目&#xff08;附 Yarn 配置与 Vite 建议&#xff09;&#x1f4f7; 创建 React 项目界面详解1️⃣ Name&#xf…...

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 镜像保存通常有两种场景&#xff1a;一种是保存镜像本身的修改&#xff08;如配置、初始化数据&#xff09;&#xff0c;另一种是持久化保存容器运行时产生的数据&#xff08;如数据库表、用户数据&#xff09;。以下是具体方法&am…...

【JVM】从零开始深度解析JVM

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

算法训练营第十四天|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单例模式总结

说明&#xff1a;单例模式的核心是确保一个类只有一个实例&#xff0c;并提供全局访问点。饿汉式和懒汉式是两种常见的实现方式 一、饿汉式和懒汉式 1. 饿汉式&#xff08;Eager Initialization&#xff09; public class EagerSingleton {// 类加载时直接初始化实例private…...

在 Elasticsearch 中删除文档中的某个字段

作者&#xff1a;来自 Elastic Kofi Bartlett 探索在 Elasticsearch 中删除文档字段的方法。 更多有关 Elasticsearch 文档的操作&#xff0c;请详细阅读文章 “开始使用 Elasticsearch &#xff08;1&#xff09;”。 想获得 Elastic 认证&#xff1f;查看下一期 Elasticsear…...

OpenCV中适用华为昇腾(Ascend)后端的逐元素操作(Per-element Operations)

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 针对华为昇腾&#xff08;Ascend&#xff09;后端的逐元素操作&#xff08;Per-element Operations&#xff09;&#xff0c;这些操作通常用于图…...

Jenkins集成Maven

一、概述 Jenkins是一个开源的持续集成工具&#xff0c;用于自动化各种开发任务。Maven是一个项目管理和构建自动化工具&#xff0c;主要用于Java项目。通过将Jenkins和Maven集成&#xff0c;可以实现自动化构建、测试和部署&#xff0c;提高开发效率和代码质量。 二、前提条…...

初识Linux · TCP基本使用 · 回显服务器

目录 前言&#xff1a; 回显服务器 TCPserver_v0 TCPserver_v1--多进程版本 TCPserver_v2--多线程版本 前言&#xff1a; 前文我们介绍了UDP的基本使用&#xff0c;本文我们介绍TCP的基本使用&#xff0c;不过TCP的使用我们这里先做一个预热&#xff0c;即只是使用TCP的A…...

Qwen:Qwen3,R1 在 Text2SQL 效果评估

【对比模型】 Qwen3 235B-A22B&#xff08;2350亿总参数&#xff0c;220亿激活参数&#xff09;&#xff0c;32B&#xff0c;30B-A3B&#xff1b;QwQ 32B&#xff08;推理模型&#xff09;DeepSeek-R1 671B&#xff08;满血版&#xff09;&#xff08;推理模型&#xff09; 1&a…...

【layout组件 与 路由镶嵌】vue3 后台管理系统

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

mobile自动化测试-appium webdriverio

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

Spring Bean有哪几种配置方式?

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

解析小米大模型MiMo:解锁语言模型推理潜力

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

证券行业数字化转型:灵雀云架设云原生“数字高速路”

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

Centos系统详解架构详解

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

【后端】SpringBoot用CORS解决无法跨域访问的问题

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

MySQL 8.0(主从复制)

MySQL 8.0 的 主从复制&#xff08;Master-Slave Replication&#xff09; 是一种数据库高可用和数据备份的核心技术&#xff0c;下面用 一、什么是主从复制&#xff1f; 就像公司的「领导-秘书」分工&#xff1a; 主库&#xff08;Master&#xff09;&#xff1a;负责处理所…...

TCPIP详解 卷1协议 十 用户数据报协议和IP分片

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

finebi使用资源迁移无法导入资源,解决方法

finebi使用资源迁移无法导入资源&#xff0c;解决方法 最近在使用finebi开发finebi报表&#xff0c;报表开发之后&#xff0c;从一台电脑将资源导入另一台电脑后&#xff0c;出现不允许导入的提示&#xff0c;如下&#xff1a; 原因&#xff1a; 两个finebi的管理员名称不一致…...

【ASR学习笔记】:语音识别领域基本术语

一、基础术语 ASR (Automatic Speech Recognition) 自动语音识别&#xff0c;把语音信号转换成文本的技术。 VAD (Voice Activity Detection) 语音活动检测&#xff0c;判断一段音频里哪里是说话&#xff0c;哪里是静音或噪音。 Acoustic Model&#xff08;声学模型&#xff0…...

精益数据分析(53/126):双边市场模式指标全解析与运营策略深度探讨

精益数据分析&#xff08;53/126&#xff09;&#xff1a;双边市场模式指标全解析与运营策略深度探讨 在创业与数据分析的探索之路上&#xff0c;深入了解各类商业模式的关键指标和运营策略至关重要。今天&#xff0c;我们依然怀揣着与大家共同进步的信念&#xff0c;深入研读…...

分布式锁redisson的中断操作

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