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

手动实现 Vue 3的简易双向数据绑定(模仿源码)

Vue 3 带来了许多令人兴奋的新特性和改进,其中之一就是其双向数据绑定的实现方式。与 Vue 2 使用 Object.defineProperty 不同,Vue 3 利用了 JavaScript 的 Proxy 特性来创建响应式数据。在这篇博客中,我们将探讨 Vue 3 中双向数据绑定的基础原理,并尝试手动实现一个简化版的这一机制。

核心概念

Vue 3 的双向绑定依赖于两个核心概念:响应式代理(Reactive Proxy)Effect 依赖收集系统

响应式代理

Vue 3 使用 Proxy 对象来创建响应式数据。这允许框架在不改变对象本身结构的情况下,拦截并跟踪属性的访问和修改。

Effect 依赖收集系统

Effect 依赖收集系统用于自动追踪响应式数据的使用情况,并在数据变化时重新执行副作用(如渲染函数)。

实现步骤

以下是手动实现 Vue 3 双向绑定的简化步骤:

1. 创建 reactive 函数

这个函数用于将普通对象转换为响应式代理。

function reactive(target) {// 使用 Proxy 对象创建响应式数据return new Proxy(target, {// 拦截对象属性的读取get(target, key, receiver) {console.log(`访问了属性:${key}`);// 使用 Reflect.get 保证 this 指向正确return Reflect.get(target, key, receiver);},// 拦截对象属性的设置set(target, key, value, receiver) {console.log(`设置了属性:${key},新值为:${value}`);// 使用 Reflect.set 设置属性值Reflect.set(target, key, value, receiver);// 此处省略了依赖通知逻辑,实际 Vue 3 中会触发更新return true;}});
}

2. 定义 effect 函数

effect 函数用于注册副作用函数,并在响应式数据变化时执行这些函数。

let activeEffect = null;function effect(fn) {// 设置当前活动的副作用函数activeEffect = fn;// 立即执行函数,用于依赖收集fn();// 重置当前活动的副作用函数activeEffect = null;
}

3. 改进 reactive 以收集依赖

现在我们需要修改 reactive 函数,以支持依赖收集和派发更新。

const targetMap = new WeakMap();function track(target, key) {// 如果没有活动的副作用函数,直接返回if (!activeEffect) return;// 从 targetMap 中获取当前对象的所有依赖let depsMap = targetMap.get(target);if (!depsMap) {// 如果没有,就创建新的 Map 并存入 targetMaptargetMap.set(target, (depsMap = new Map()));}// 获取当前属性的所有依赖let dep = depsMap.get(key);if (!dep) {// 如果没有,就创建新的 Set 并存入 depsMapdepsMap.set(key, (dep = new Set()));}// 将当前活动的副作用函数添加到依赖集合中dep.add(activeEffect);
}function trigger(target, key) {// 从 targetMap 中获取对象的所有依赖const depsMap = targetMap.get(target);if (!depsMap) return;// 获取当前属性的所有依赖let dep = depsMap.get(key);if (dep) {// 对于每一个依赖(即副作用函数),执行它dep.forEach(effect => effect());}
}// 更新 reactive 函数
function 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;}});
}

4. 测试实例

创建一个响应式对象,并观察它的变化。

const state = reactive({ count: 0 });// 注册一个副作用函数来模拟渲染逻辑
effect(() => {console.log(`count is now: ${state.count}`);
});// 更改响应式数据的属性,触发副作用函数
state.count++;

完整代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue 3 简易双向数据绑定测试</title>
</head>
<body><h1>Vue 3 简易双向数据绑定测试</h1><div id="app"><p>Count: <span id="count">0</span></p><button id="increment">Increment</button></div><script>// reactive 函数function reactive(target) {const handler = {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;}};return new Proxy(target, handler);}let activeEffect = null;function effect(fn) {activeEffect = fn;fn();activeEffect = null;}const targetMap = new WeakMap();function track(target, key) {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);}function trigger(target, key) {const depsMap = targetMap.get(target);if (!depsMap) return;let dep = depsMap.get(key);if (dep) {dep.forEach(effect => effect());}}// 测试代码const state = reactive({ count: 0 });effect(() => {document.getElementById('count').innerText = state.count;});document.getElementById('increment').addEventListener('click', () => {state.count++;});</script>
</body>
</html>

在这个文件中,我们创建了一个按钮,每当它被点击时,就会增加 state.count 的值。这个值的变化会触发绑定的副作用函数,从而更新显示在页面上的计数结论

小结

以上代码展示了一个 Vue 3 中双向数据绑定的简化实现。通过 Proxy 和动态依赖收集系统,我们可以创建一个灵活且强大的响应式系统。这种实现方式使得 Vue 3 在处理复杂应用时更为高效和灵活。

相关文章:

手动实现 Vue 3的简易双向数据绑定(模仿源码)

Vue 3 带来了许多令人兴奋的新特性和改进&#xff0c;其中之一就是其双向数据绑定的实现方式。与 Vue 2 使用 Object.defineProperty 不同&#xff0c;Vue 3 利用了 JavaScript 的 Proxy 特性来创建响应式数据。在这篇博客中&#xff0c;我们将探讨 Vue 3 中双向数据绑定的基础…...

LVS最终奥义之DR直接路由模式

1 LVS-DR(直接路由模式) 1.1 LVS-DR模式工作过程 1.客户端通过VIP将访问请求报文&#xff08;源IP为客户端IP&#xff0c;目标IP为VIP&#xff09;发送到调度器 2.调度器通过调度算法选择最适合的节点服务器并重新封装数据报文&#xff08;将源mac地址改为调度器的mac地址&am…...

t-SNE高维数据可视化实例

t-SNE&#xff1a;高维数据分布可视化 实例1&#xff1a;自动生成一个S形状的三维曲线 实例1结果&#xff1a; 实例1完整代码&#xff1a; import matplotlib.pyplot as plt from sklearn import manifold, datasets """对S型曲线数据的降维和可视化"&q…...

配置应用到k8s

配置应用到k8s&#xff0c;前置条件是安装了Docker&#xff0c;Minikube&#xff0c;kubectl 应用已经通过Docker生成本地镜像文件 1&#xff0c;创建godemo-deployment.yaml apiVersion: apps/v1kind: Deploymentmetadata:name: godemo-deploymentspec:replicas: 3 #启动三个…...

(四)STM32 操作 GPIO 点亮 LED灯 / GPIO工作模式

目录 1. STM32 工程模板中的工程目录介绍 2. GPIO 简介 3. GPIO 框图剖析 1&#xff09;保护二极管及上、下拉电阻 2&#xff09; P-MOS 管和 N-MOS 管 3&#xff09;输出数据寄存器 3.1&#xff09;ODR 端口输出数据寄存器 3.2&#xff09;BSRR 端口位设置/清除寄存器 4&a…...

你知道跨站脚本攻击吗?一篇带你了解什么叫做XSS

1.XSS简介 &#xff08;1&#xff09;XSS简介 XSS作为OWASP TOP 10之一。 XSS中文叫做跨站脚本攻击&#xff08;Cross-site scripting&#xff09;&#xff0c;本名应该缩写为CSS&#xff0c;但是由于CSS&#xff08;Cascading Style Sheets&#xff0c;层叠样式脚本&#x…...

JVM入门

JVM概述 JVM位置 JVM体系结构 注意&#xff1a;栈中一定不存在垃圾&#xff0c;栈中数据用完一个弹出一个&#xff0c;总结来说&#xff0c;栈区、本地方法栈、程序计数器这三块必定不存在垃圾。JVM调优主要是针对方法区、堆&#xff08;99%&#xff09;进行调优。 常用的第三…...

Cmake基础(5)

这篇文章主要描述如何使用cmake构建一个库工程 文章目录 add_libraryinstall 库工程的代码&#xff1a;头文件和源文件 #ifndef ADD_H #define ADD_H#ifdef _WIN32 #ifdef MYMATH_EXPORTS #define MYMATH_API __declspec(dllexport) #else #define MYMATH_API __declspec(dll…...

Rabbitmq 死信取消超时订单

本文使用的版本 otp_win64_25.0rabbitmq-server-3.11.26rabbitmq插件 rabbitmq_delayed_message_exchange-3.11.1 pom.xml文件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> …...

C语言—每日选择题—Day55

指针相关博客 打响指针的第一枪&#xff1a;指针家族-CSDN博客 深入理解&#xff1a;指针变量的解引用 与 加法运算-CSDN博客 第一题 1. 若有如下定义&#xff0c;则 p1&m&#xff1b;p2p1&#xff1b; 是正确赋值语句.说法是否正确&#xff1f; int *p1; int *p2; int m …...

软件测试岗位的简历怎么写?项目怎么包装

已经帮大家打包好了包装好的简历模板&#xff0c;大家可以直接进行套用&#xff0c;详情请望下看 自动化测试相关教程推荐&#xff1a; 2023最新自动化测试自学教程新手小白26天入门最详细教程,目前已有300多人通过学习这套教程入职大厂&#xff01;&#xff01;_哔哩哔哩_bili…...

服务器解析漏洞是什么?攻击检测及修复

服务器解析漏洞&#xff08;Server-side Include Vulnerability&#xff0c;SSI漏洞&#xff09;是一种安全漏洞&#xff0c;通常出现在支持服务器端包含&#xff08;SSI&#xff09;功能的Web服务器上。SSI是一种在Web页面中嵌入动态内容的技术&#xff0c;允许开发人员将外部…...

HTML---CSS美化网页元素

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.div 标签&#xff1a; <div>是HTML中的一个常用标签&#xff0c;用于定义HTML文档中的一个区块&#xff08;或一个容器&#xff09;。它可以包含其他HTML元素&#xff0c;如文本、图像…...

【Docker】基础篇

文章目录 Docker为什么出现容器和虚拟机关于虚拟机关于Docker二者区别&#xff1a; Docker的基本组成相关概念-镜像&#xff0c;容器&#xff0c;仓库安装Docker卸载docker阿里云镜像加速docker run的原理**为什么容器比虚拟机快**Docker的常用命令1.帮助命令2.镜像相关命令3.容…...

Potplayer播放器远程访问群晖WebDav本地资源【内网穿透】

文章目录 本教程解决的问题是&#xff1a;按照本教程方法操作后&#xff0c;达到的效果是&#xff1a;1 使用环境要求&#xff1a;2 配置webdav3 测试局域网使用potplayer访问webdav3 内网穿透&#xff0c;映射至公网4 使用固定地址在potplayer访问webdav 国内流媒体平台的内容…...

【神经网络】imshow展示图片报错

文章目录 代码示例报错信息报错原因解决方法其他问题 代码示例 plt.imshow(np.squeeze(images[0]))报错信息 Invalid shape (3, 60, 90) for image data报错原因 格式错误&#xff0c;输入具有RGB值的图像&#xff0c;输入三维数组参数的格式应该是&#xff08;高度&#xf…...

【C++】对象特性:无参有参构造函数,拷贝构造函数,析构函数

目录 对象的初始化和清理1.1 构造函数和析构函数1.2 构造函数的分类及调用1.3 拷贝构造函数调用时机1.4 构造函数调用规则1.5 深拷贝与浅拷贝 对象的初始化和清理 生活中我们买的电子产品都基本会有出厂设置&#xff0c;在某一天我们不用时候也会删除一些自己信息数据保证安全。…...

【算法与数据结构】1005、LeetCode K 次取反后最大化的数组和

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题允许某个下标的数字多次翻转&#xff0c;因此思路比较简单。首先&#xff0c;我们要求最大和&…...

作业--day34

使用select完成TCP并发服务器和客户端 server.c #include <myhead.h>#define PORT 8888 #define IP "192.168.125.137"int main(int argc, const char *argv[]) {int sfd socket(AF_INET, SOCK_STREAM, 0);if(sfd -1){perror("socket error");re…...

车辆违规开启远光灯检测系统:融合YOLO-MS改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着社会的不断发展和交通工具的普及&#xff0c;车辆违规行为成为了一个严重的问题。其中&#xff0c;车辆违规开启远光灯是一种常见的违规行为&#xff0c;给其…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

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

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

学习一下用鸿蒙​​DevEco Studio HarmonyOS5实现百度地图

在鸿蒙&#xff08;HarmonyOS5&#xff09;中集成百度地图&#xff0c;可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API&#xff0c;可以构建跨设备的定位、导航和地图展示功能。 ​​1. 鸿蒙环境准备​​ ​​开发工具​​&#xff1a;下载安装 ​​De…...