Vue2 响应式系统设计原理与实现

文章目录

  • Vue2 响应式系统设计原理与实现

Vue2 响应式系统设计原理与实现

Vue2 的响应式原理主要基于以下几点:

使用 Object.defineProperty () 方法对数据对象的属性进行劫持
当数据发生变化时,通知依赖该数据的视图进行更新
实现一个发布 - 订阅模式,包含 Watcher(订阅者)、Dep(依赖收集器)等核心概念

创建以下几个核心部分:

Observer:递归地将数据对象的所有属性转换为响应式
Dep:依赖收集器,负责收集和通知订阅者
Watcher:订阅者,当数据变化时执行相应的回调函数
下面我们来简单实现以下

// 依赖收集器类:管理某个数据的所有依赖(订阅者)
class Dep {// 构造函数初始化constructor() {// 存储所有订阅者的数组this.subscribers = [];}// 添加订阅者到收集器addSub(sub) {// 检查订阅者是否存在且有update方法if (sub && sub.update) {this.subscribers.push(sub);}}// 通知所有订阅者数据已更新notify() {// 遍历所有订阅者并调用其update方法this.subscribers.forEach(sub => {sub.update();});}
}// 订阅者类:代表一个依赖,数据变化时执行更新操作
class Watcher {// 构造函数:接收Vue实例、属性名和回调函数constructor(vm, key, callback) {this.vm = vm;         // 存储Vue实例的引用this.key = key;       // 要监视的数据属性名this.callback = callback; // 数据变化时要执行的回调函数this.value = this.get();  // 初始化时获取值,触发getter完成依赖收集}// 获取数据并将当前订阅者添加到依赖收集器get() {// 将当前订阅者设为Dep的目标,标记为当前需要收集的依赖Dep.target = this;// 访问数据属性,触发其getter,从而完成依赖收集const value = this.vm[this.key];// 重置Dep.target,避免后续操作错误收集依赖Dep.target = null;// 返回获取到的值return value;}// 数据变化时执行的更新方法update() {// 获取新值const newValue = this.get();// 保存旧值const oldValue = this.value;// 只有当新旧值不同时才执行回调if (newValue !== oldValue) {// 更新当前值为新值this.value = newValue;// 调用回调函数,并将Vue实例作为上下文,传入新值和旧值this.callback.call(this.vm, newValue, oldValue);}}
}// 将普通对象转换为响应式对象的函数
function observe(data) {// 如果数据不是对象或为null,则无需处理if (!data || typeof data !== 'object') {return;}// 创建观察者实例处理数据return new Observer(data);
}// 观察者类:负责将对象的所有属性转换为响应式
class Observer {// 构造函数:接收需要处理的数据对象constructor(data) {this.data = data;// 遍历对象属性并处理this.walk(data);}// 遍历对象的所有属性walk(data) {// 获取对象所有自有属性的键名Object.keys(data).forEach(key => {// 为每个属性定义响应式this.defineReactive(data, key, data[key]);});}// 核心方法:使用Object.defineProperty定义响应式属性defineReactive(obj, key, val) {// 为当前属性创建一个依赖收集器const dep = new Dep();// 如果属性值是对象,递归处理使其也成为响应式observe(val);// 使用Object.defineProperty劫持属性的getter和setterObject.defineProperty(obj, key, {enumerable: true,   // 允许属性被枚举(例如在for...in循环中)configurable: true, // 允许属性被配置(例如删除属性)// 当属性被访问时触发的getterget() {// 如果当前有需要收集的依赖(Dep.target存在)if (Dep.target) {// 将当前依赖添加到收集器中dep.addSub(Dep.target);}// 返回属性值return val;},// 当属性被修改时触发的setterset(newVal) {// 如果新值和旧值相同,则不做处理if (newVal === val) {return;}// 更新属性值val = newVal;// 如果新值是对象,需要将其转换为响应式observe(newVal);// 通知所有依赖当前属性的订阅者数据已更新dep.notify();}});}
}// 简化版Vue类:整合响应式系统
class Vue {// 构造函数:接收配置选项constructor(options) {this.$options = options;  // 存储配置选项this.$data = options.data; // 存储数据对象// 将数据转换为响应式observe(this.$data);// 将data中的属性代理到Vue实例上,方便直接访问this.proxyData(this.$data);// 如果有created生命周期钩子,执行它if (options.created) {options.created.call(this);}}// 数据代理方法:使vm.xxx等价于vm.$data.xxxproxyData(data) {// 遍历data的所有属性Object.keys(data).forEach(key => {// 在Vue实例上定义与data属性同名的属性Object.defineProperty(this, key, {// 当访问vm.xxx时,返回vm.$data.xxx的值get() {return data[key];},// 当修改vm.xxx时,同步修改vm.$data.xxxset(newVal) {data[key] = newVal;}});});}// 提供$watch方法,用于监视数据变化$watch(key, callback) {// 创建一个新的订阅者,关联到指定的属性和回调new Watcher(this, key, callback);}
}// 使用示例
const vm = new Vue({data: {message: 'Hello Vue',count: 0,user: {name: 'John'}},created() {console.log('初始化完成:', this.message);}
});// 添加监听器,当message变化时触发
vm.$watch('message', (newVal, oldVal) => {console.log(`message变化: ${oldVal} -> ${newVal}`);
});// 添加监听器,当count变化时触发
vm.$watch('count', (newVal, oldVal) => {console.log(`count变化: ${oldVal} -> ${newVal}`);
});// 添加监听器,当user.name变化时触发
vm.$watch('user.name', (newVal, oldVal) => {console.log(`user.name变化: ${oldVal} -> ${newVal}`);
});// 测试数据变化,观察是否触发更新
vm.message = 'Hello World'; // 触发message的更新
vm.count = 1; // 触发count的更新
vm.user.name = 'Jane'; // 触发user.name的更新

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/919843.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/919843.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索 JUC:Java 并发编程的神奇世界

探索 JUC:Java 并发编程的神奇世界 在 Java 编程领域,随着多核处理器的普及和应用场景复杂度的提升,并发编程变得愈发重要。Java 并发包(JUC,Java.util.concurrent)就像是一座宝藏库,为开发者提…

selenium采集数据怎么应对反爬机制?

selenium是一个非常强大的浏览器自动化工具,通过操作浏览器来抓取动态网页内容,可以很好的处理JavaScript和AJAX加载的网页。 它能支持像点击按钮、悬停元素、填写表单等各种自动化操作,所以很适合自动化测试和数据采集。 selenium与各种主流…

指定文件夹上的压缩图像格式tiff转换为 jpg 批量脚本

文章大纲 背景简介 代码 背景简介 随着数字成像技术在科研、医学影像和遥感等领域的广泛应用,多页TIFF(Tag Image File Format)文件因其支持多维数据存储和高位深特性,成为存储序列图像、显微镜切片或卫星遥感数据的首选格式。然而在实际应用中,这类文件存在以下显著痛点…

Docker 部署 MySQL 8.0 完整指南:从拉取镜像到配置远程访问

目录前言一、拉取镜像二、查看镜像三、运行容器命令参数说明:四、查看运行容器五、进入容器内部六、修改 MySQL 配置1. 创建配置文件2. 配置内容七、重启 MySQL 服务八、设置 Docker 启动时自动启动 MySQL九、再次重启 MySQL十、授权远程访问1. 进入容器内部2. 登录…

IntelliJ IDEA 常用快捷键笔记(Windows)

前言:特别标注的快捷键(Windows)快捷键功能说明Ctrl Alt M将选中代码提取成方法Ctrl Alt T包裹选中代码块(try/catch、if、for 等)Ctrl H查看类的继承层次Alt 7打开项目结构面板Ctrl F12打开当前文件结构视图Ct…

疏老师-python训练营-Day54Inception网络及其思考

浙大疏锦行 DAY54 一、 inception网络介绍 今天我们介绍inception,也就是GoogleNet 传统计算机视觉的发展史 从上面的链接,可以看到其实inceptionnet是在resnet之前的,那为什么我今天才说呢?因为他要引出我们后面的特征融合和…

LeetCode第3304题 - 找出第 K 个字符 I

题目 解答 class Solution {public char kthCharacter(int k) {int n 0;int v 1;while (v < k) {v << 1;n;}String target kthCharacterString(n);return target.charAt(k - 1);}public String kthCharacterString(int n) {if (n 0) {return "a";}Str…

Codeforces Round 1043 (Div. 3) D-F 题解

D. From 1 to Infinity 题意 有一个无限长的序列&#xff0c;是把所有正整数按次序拼接&#xff1a;123456789101112131415...\texttt{123456789101112131415...}123456789101112131415...。求这个序列前 k(k≤1015)k(k\le 10^{15})k(k≤1015) 位的数位和。 思路 二分出第 …

【C语言16天强化训练】从基础入门到进阶:Day 7

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题、洛谷刷题、C/C基础知识知识强化补充、C/C干货分享&学习过程记录 &#x1f349;学习方向&#xff1a;C/C方向学习者…

【AI基础:神经网络】16、神经网络的生理学根基:从人脑结构到AI架构,揭秘道法自然的智能密码

“道法自然,久藏玄冥”——人工神经网络(ANN)的崛起并非偶然,而是对自然界最精妙的智能系统——人脑——的深度模仿与抽象。从单个神经元的信号处理到大脑皮层的层级组织,从突触可塑性的学习机制到全脑并行计算的高效能效,生物大脑的“玄冥”智慧为AI提供了源源不断的灵感…

容器安全实践(一):概念篇 - 从“想当然”到“真相”

在容器化技术日益普及的今天&#xff0c;许多开发者和运维人员都将应用部署在 Docker 或 Kubernetes 中。然而&#xff0c;一个普遍存在的误解是&#xff1a;“容器是完全隔离的&#xff0c;所以它是安全的。” 如果你也有同样的想法&#xff0c;那么你需要重新审视容器安全了。…

腾讯开源WeKnora:新一代文档理解与检索框架

引言&#xff1a;文档智能处理的新范式 在数字化时代&#xff0c;企业和个人每天都面临着海量文档的处理需求&#xff0c;从产品手册到学术论文&#xff0c;从合同条款到医疗报告&#xff0c;非结构化文档的高效处理一直是技术痛点。2025年8月&#xff0c;腾讯正式开源了基于大…

C++之list类的代码及其逻辑详解 (中)

接下来我会依照前面所说的一些接口以及list的结构来进行讲解。1. list_node的结构1.1 list_node结构体list由于其结构为双向循环链表&#xff0c;所以我们在这里要这么初始化_next&#xff1a;指向链表中下一个节点的指针_prev&#xff1a;指向链表中上一个节点的指针_val&…

新能源汽车热管理仿真:蒙特卡洛助力神经网络训练

研究背景在新能源汽车的热管理仿真研究中&#xff0c;神经网络训练技术常被应用于系统降阶建模。通过这一方法&#xff0c;可以构建出高效准确的代理模型&#xff0c;进而用于控制策略的优化、系统性能的预测与评估&#xff0c;以及实时仿真等任务&#xff0c;有效提升开发效率…

第十九讲:C++11第一部分

目录 1、C11简介 2、列表初始化 2.1、{}初始化 2.2、initializer_list 2.2.1、成员函数 2.2.2、应用 3、变量类型推导 3.1、auto 3.2、decltype 3.3、nullptr 4、范围for 5、智能指针 6、STL的一些变化 7、右值引用和移动语义 7.1、右值引用 7.2、右值与左值引…

书写本体论视域下的文字学理论重构

在符号学与哲学的交叉领域&#xff0c;文字学&#xff08;Grammatologie&#xff09;作为一门颠覆性学科始终处于理论风暴的中心。自德里达1967年发表《论文字学》以来&#xff0c;传统语言学中"语音中心主义"的霸权地位遭遇根本性动摇&#xff0c;文字不再被视为语言…

为什么要做架构设计?架构设计包含哪些内容?

大家好,我是IT孟德,You can call me Aman(阿瞒,阿弥陀佛的ē,Not阿门的ā),一个喜欢所有对象(热爱技术)的男人。我正在创作架构专栏,秉承ITer开源精神分享给志同道合(爱江山爱技术更爱美人)的朋友。专栏更新不求速度但求质量(曹大诗人传世作品必属精品,请脑补一下《…

Vue2封装Axios

一、介绍Axios 是一个基于 promise 的 HTTP 库&#xff0c;简单的讲就是可以发送get、post等请求。二、安装npm install axios --save二、axios不同请求方式axios(config)这是 Axios 的核心方法&#xff0c;用于发送自定义配置的 HTTP 请求。通过传入一个包含请求配置的对象&am…

DataAnalytics之Tool:Metabase的简介、安装和使用方法、案例应用之详细攻略

DataAnalytics之Tool&#xff1a;Metabase的简介、安装和使用方法、案例应用之详细攻略 目录 Metabase的简介 1、特点 Metabase的安装和使用方法 1、安装 快速设置&#xff1a;开发环境 前端快速设置 后端快速设置 2、使用方法 Metabase的案例应用 Metabase的简介 Met…

frp v0.64.0 更新:开源内网穿透工具,最简洁教程

frp是一款跨平台的内网穿透工具&#xff0c;支持 Windows、macOS 与 Linux&#xff0c;它需要你有一台拥有固定公网 IP 的电脑&#xff0c;VPS 最好&#xff0c;然后就能愉快的进行内网穿透了。还支持 https&#xff0c;甚至可以用它进行小程序开发。Appinn v0.64.0 新增token…