手动实现 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 带来了许多令人兴奋的新特性和改进,其中之一就是其双向数据绑定的实现方式。与 Vue 2 使用 Object.defineProperty 不同,Vue 3 利用了 JavaScript 的 Proxy 特性来创建响应式数据。在这篇博客中,我们将探讨 Vue 3 中双向数据绑定的基础…...
LVS最终奥义之DR直接路由模式
1 LVS-DR(直接路由模式) 1.1 LVS-DR模式工作过程 1.客户端通过VIP将访问请求报文(源IP为客户端IP,目标IP为VIP)发送到调度器 2.调度器通过调度算法选择最适合的节点服务器并重新封装数据报文(将源mac地址改为调度器的mac地址&am…...
t-SNE高维数据可视化实例
t-SNE:高维数据分布可视化 实例1:自动生成一个S形状的三维曲线 实例1结果: 实例1完整代码: import matplotlib.pyplot as plt from sklearn import manifold, datasets """对S型曲线数据的降维和可视化"&q…...
配置应用到k8s
配置应用到k8s,前置条件是安装了Docker,Minikube,kubectl 应用已经通过Docker生成本地镜像文件 1,创建godemo-deployment.yaml apiVersion: apps/v1kind: Deploymentmetadata:name: godemo-deploymentspec:replicas: 3 #启动三个…...
(四)STM32 操作 GPIO 点亮 LED灯 / GPIO工作模式
目录 1. STM32 工程模板中的工程目录介绍 2. GPIO 简介 3. GPIO 框图剖析 1)保护二极管及上、下拉电阻 2) P-MOS 管和 N-MOS 管 3)输出数据寄存器 3.1)ODR 端口输出数据寄存器 3.2)BSRR 端口位设置/清除寄存器 4&a…...
你知道跨站脚本攻击吗?一篇带你了解什么叫做XSS
1.XSS简介 (1)XSS简介 XSS作为OWASP TOP 10之一。 XSS中文叫做跨站脚本攻击(Cross-site scripting),本名应该缩写为CSS,但是由于CSS(Cascading Style Sheets,层叠样式脚本&#x…...
JVM入门
JVM概述 JVM位置 JVM体系结构 注意:栈中一定不存在垃圾,栈中数据用完一个弹出一个,总结来说,栈区、本地方法栈、程序计数器这三块必定不存在垃圾。JVM调优主要是针对方法区、堆(99%)进行调优。 常用的第三…...
Cmake基础(5)
这篇文章主要描述如何使用cmake构建一个库工程 文章目录 add_libraryinstall 库工程的代码:头文件和源文件 #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
指针相关博客 打响指针的第一枪:指针家族-CSDN博客 深入理解:指针变量的解引用 与 加法运算-CSDN博客 第一题 1. 若有如下定义,则 p1&m;p2p1; 是正确赋值语句.说法是否正确? int *p1; int *p2; int m …...
软件测试岗位的简历怎么写?项目怎么包装
已经帮大家打包好了包装好的简历模板,大家可以直接进行套用,详情请望下看 自动化测试相关教程推荐: 2023最新自动化测试自学教程新手小白26天入门最详细教程,目前已有300多人通过学习这套教程入职大厂!!_哔哩哔哩_bili…...
服务器解析漏洞是什么?攻击检测及修复
服务器解析漏洞(Server-side Include Vulnerability,SSI漏洞)是一种安全漏洞,通常出现在支持服务器端包含(SSI)功能的Web服务器上。SSI是一种在Web页面中嵌入动态内容的技术,允许开发人员将外部…...
HTML---CSS美化网页元素
文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据总结 一.div 标签: <div>是HTML中的一个常用标签,用于定义HTML文档中的一个区块(或一个容器)。它可以包含其他HTML元素,如文本、图像…...
【Docker】基础篇
文章目录 Docker为什么出现容器和虚拟机关于虚拟机关于Docker二者区别: Docker的基本组成相关概念-镜像,容器,仓库安装Docker卸载docker阿里云镜像加速docker run的原理**为什么容器比虚拟机快**Docker的常用命令1.帮助命令2.镜像相关命令3.容…...
Potplayer播放器远程访问群晖WebDav本地资源【内网穿透】
文章目录 本教程解决的问题是:按照本教程方法操作后,达到的效果是:1 使用环境要求:2 配置webdav3 测试局域网使用potplayer访问webdav3 内网穿透,映射至公网4 使用固定地址在potplayer访问webdav 国内流媒体平台的内容…...
【神经网络】imshow展示图片报错
文章目录 代码示例报错信息报错原因解决方法其他问题 代码示例 plt.imshow(np.squeeze(images[0]))报错信息 Invalid shape (3, 60, 90) for image data报错原因 格式错误,输入具有RGB值的图像,输入三维数组参数的格式应该是(高度…...
【C++】对象特性:无参有参构造函数,拷贝构造函数,析构函数
目录 对象的初始化和清理1.1 构造函数和析构函数1.2 构造函数的分类及调用1.3 拷贝构造函数调用时机1.4 构造函数调用规则1.5 深拷贝与浅拷贝 对象的初始化和清理 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全。…...
【算法与数据结构】1005、LeetCode K 次取反后最大化的数组和
文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析:本题允许某个下标的数字多次翻转,因此思路比较简单。首先,我们要求最大和&…...
作业--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 研究背景与意义 随着社会的不断发展和交通工具的普及,车辆违规行为成为了一个严重的问题。其中,车辆违规开启远光灯是一种常见的违规行为,给其…...
TradingAgents-CN智能交易系统:3种部署方案让你5分钟开启AI投资分析
TradingAgents-CN智能交易系统:3种部署方案让你5分钟开启AI投资分析 【免费下载链接】TradingAgents-CN 基于多智能体LLM的中文金融交易框架 - TradingAgents中文增强版 项目地址: https://gitcode.com/GitHub_Trending/tr/TradingAgents-CN 还在为复杂的金融…...
Python新手福音:借助快马AI零基础构建你的第一个行情网站
作为一个刚接触Python的新手,想要构建一个行情网站听起来可能有点吓人。但通过InsCode(快马)平台的AI辅助,整个过程变得异常简单。下面我就分享一下自己从零开始搭建第一个行情网站的经历。 数据获取部分 首先需要找到一个免费的金融数据接口。我选择了一…...
告别云端依赖:AnythingLLM本地Whisper实现完全离线语音转文字
告别云端依赖:AnythingLLM本地Whisper实现完全离线语音转文字 【免费下载链接】anything-llm The all-in-one AI productivity accelerator. On device and privacy first with no annoying setup or configuration. 项目地址: https://gitcode.com/GitHub_Trendi…...
Qwen3.5-9B-AWQ-4bit多场景落地:零售货架图分析+缺货识别+SKU自动计数
Qwen3.5-9B-AWQ-4bit多场景落地:零售货架图分析缺货识别SKU自动计数 1. 零售场景中的视觉理解挑战 在零售行业,货架管理一直是运营效率的关键指标。传统的人工巡检方式存在几个明显痛点: 效率低下:一个中型超市需要2-3小时完成…...
RK3568交叉编译环境搭建:ARM官方GCC 8.3与Linaro版本到底怎么选?我的踩坑与选择心得
RK3568交叉编译环境搭建:ARM官方GCC 8.3与Linaro版本深度对比与实战选择指南 在嵌入式开发领域,交叉编译环境的搭建往往是项目启动的第一道门槛。对于RK3568这样的高性能ARM处理器,选择合适的交叉编译器不仅关系到开发效率,更直接…...
[Windows 驱动] 深入解析进程名获取的多种内核方法
1. Windows驱动开发中的进程名获取基础 在Windows内核驱动开发中,获取进程名是最基础但至关重要的操作之一。想象一下,你正在开发一个安全监控驱动,需要实时检查哪些进程正在运行;或者你在开发一个性能优化工具,需要针…...
Linux环境下Python段错误全解析:从内存管理到线程安全的避坑手册
Linux环境下Python段错误全解析:从内存管理到线程安全的避坑手册 当你在深夜调试一个复杂的Python项目时,突然看到屏幕上跳出"Segmentation fault (core dumped)"的提示,那种感觉就像在高速公路上爆胎——明明代码逻辑看起来没问题…...
避坑指南:QT5的QListView复选框居中/对齐问题解决方案(含TableView对比)
QT5复选框对齐终极指南:从QListView到TableView的完美排版方案 在QT5界面开发中,复选框控件的视觉对齐问题堪称"程序员强迫症终结者"——明明功能已经实现,却总在UI细节上栽跟头。本文将带您深入解决QListView和TableView中复选框居…...
终极Windows系统清理指南:免费工具让电脑重获新生
终极Windows系统清理指南:免费工具让电脑重获新生 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服! 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner 您的Windows电脑是否变得越来越慢?C盘空…...
手把手教你部署DeepSeek-OCR:零基础实现多语言文字识别
手把手教你部署DeepSeek-OCR:零基础实现多语言文字识别 1. 为什么选择DeepSeek-OCR 在数字化时代,文字识别技术已经成为各行各业的基础需求。无论是扫描文档转电子版,还是从照片中提取文字信息,传统OCR工具往往在复杂场景下表现…...
