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

深入剖析 Vue 的响应式原理:构建高效 Web 应用的基石

深入剖析 Vue 的响应式原理:构建高效 Web 应用的基石

在前端开发的广阔天地里,Vue.js 凭借其简洁易用的特性和强大的功能,成为众多开发者的心头好。其中,响应式原理作为 Vue 的核心亮点之一,让数据与视图之间实现了高效的自动同步,极大地提升了开发体验和应用性能。今天,就让我们深入探究 Vue 响应式原理背后的奥秘。

 

一、什么是响应式编程

在前端领域,响应式编程是一种编程范式,它赋予程序对数据变化做出自动反应的能力。在 Vue 的世界里,这种反应体现得淋漓尽致。想象一下,在一个电商应用中,商品的库存数量是一个数据变量。当库存数量发生变化时,页面上显示库存的区域能够实时更新,无需开发者手动操作 DOM 元素来修改显示内容,这就是响应式编程的魅力所在。它让数据和视图之间建立起一种紧密的联系,数据的任何变动都能即时反映在视图上,反之亦然。

 

二、Vue 响应式原理概述

Vue 的响应式系统是其实现数据驱动视图更新的关键,主要依赖数据劫持和发布 - 订阅模式这两大核心技术,它们协同工作,构建出了一套高效的响应式机制。

  1. 数据劫持:Vue 借助 JavaScript 的Object.defineProperty()方法来实现数据劫持。这个方法可以在对象属性的读取(get)和写入(set)操作上设置拦截器。当访问对象的某个属性时,get方法会被触发;而当修改该属性时,set方法则会发挥作用。通过这种方式,Vue 能够监听对象属性的访问和修改操作,从而为后续的依赖收集和变更通知奠定基础。
  2. 依赖收集:在组件渲染过程中,Vue 会遍历组件模板中使用到的数据属性,为每个属性收集依赖关系。简单来说,就是记录哪些组件依赖了哪些数据。这些依赖关系被存储在一个依赖管理器(Dep)中,Dep就像是一个数据与组件之间的桥梁,它知道哪些组件依赖了特定的数据,以便在数据变化时能够准确通知到这些组件。
  3. 变更通知:当数据发生修改时,Vue 会调用之前设置的setter方法。setter方法会通知所有依赖于该数据的组件进行重新渲染。这就好比一个消息广播中心,一旦数据有了变动,它就会向所有相关组件发送通知,让它们及时更新自己的状态,保证视图与数据的一致性。

 

三、创建响应式对象的详细过程

下面通过一个详细的代码示例,深入理解 Vue 如何将普通对象转变为响应式对象。

function defineReactive(obj, key, val) {// 创建一个依赖收集者Dep实例const dep = new Dep(); Object.defineProperty(obj, key, {get() {// 如果Dep.target存在(即当前正在进行依赖收集),将当前的watcher添加到依赖中if (Dep.target) { dep.depend(); }return val;},set(newVal) {// 比较新值和旧值,如果不同则进行更新操作if (newVal!== val) { val = newVal; // 通知所有依赖这个值的watcher进行更新dep.notify(); }}});
}// 定义依赖收集者Dep类
class Dep {constructor() {// 用于存储依赖该数据的watcherthis.subscribers = []; }depend() {// 如果Dep.target存在,将其添加到依赖列表中if (Dep.target) { this.subscribers.push(Dep.target); }}notify() {// 遍历所有依赖,调用它们的update方法进行更新this.subscribers.forEach(sub => sub.update()); }
}// 定义一个空对象
const data = {};
// 将data对象的name属性设置为响应式,初始值为'John Doe'
defineReactive(data, 'name', 'John Doe'); // 测试反应
console.log(data.name); // 输出: John Doe
data.name = 'Jane Doe'; // 修改数据
console.log(data.name); // 输出: Jane Doe

在上述代码中,defineReactive函数承担了将对象属性转变为响应式的重任。get方法负责在数据被访问时进行依赖收集,而set方法则在数据更新时通知依赖更新。Dep类作为依赖收集和通知的管理者,维护着数据与watcher之间的关系。

结合实际 Vue 项目的代码示例

在一个简单的 Vue 组件中,我们可以这样运用响应式原理:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue响应式示例</title>
</head>
<body><div id="app"><p>{{message}}</p><button @click="changeMessage">修改消息</button></div><script>// 模拟Vue响应式数据创建过程const data = {message: '初始消息'};function defineReactive(obj, key, val) {const dep = new Dep();Object.defineProperty(obj, key, {get() {if (Dep.target) {dep.depend();}return val;},set(newVal) {if (newVal!== val) {val = newVal;dep.notify();}}});}class Dep {constructor() {this.subscribers = [];}depend() {if (Dep.target) {this.subscribers.push(Dep.target);}}notify() {this.subscribers.forEach(sub => sub.update());}}class Watcher {constructor(vm, expOrFn, cb) {this.vm = vm;this.cb = cb;this.getter = this.parseGetter(expOrFn);this.value = this.get();}get() {Dep.target = this;const value = this.getter.call(this.vm, this.vm);Dep.target = null;return value;}update() {const oldValue = this.value;this.value = this.get();this.cb.call(this.vm, this.value, oldValue);}parseGetter(expOrFn) {if (typeof expOrFn === 'function') {return expOrFn;}const path = expOrFn.split('.');return function(obj) {for (let i = 0; i < path.length; i++) {if (!obj) return;obj = obj[path[i]];}return obj;};}}defineReactive(data,'message', data.message);const vm = {_data: data};new Watcher(vm,'message', (newValue) => {const app = document.getElementById('app');app.querySelector('p').textContent = newValue;});vm.changeMessage = function() {this._data.message = '修改后的消息';};</script>
</body>
</html>

在这个示例中,我们模拟了 Vue 的响应式数据创建和更新过程。通过点击按钮,触发changeMessage方法修改数据,进而触发Watcher的更新,实现视图的自动更新。

 

四、依赖管理的深入理解

在 Vue 中,watcher(依赖项)在响应式系统中扮演着至关重要的角色,它负责具体的更新逻辑。下面是一个简单的Watcher类示例及其详细解析。

class Watcher {constructor(fn) {this.fn = fn;// 使用Set数据结构存储依赖的ID,确保唯一性this.depIds = new Set(); // 触发getter,开始收集依赖this.get(); }get() {// 将当前watcher设置为Dep.target,以便在依赖收集时能够正确识别Dep.target = this; // 执行传入的函数,从而触发数据的访问,进行依赖收集this.fn(); // 清除Dep.target,避免影响后续操作Dep.target = null; }update() {console.log('数据更新,视图重新渲染');// 重新执行get方法,再次收集依赖并更新相关数据this.get(); }
}// 使用示例
const watcher = new Watcher(() => {console.log('当前姓名: ', data.name);
});
data.name = 'Alice'; // 数据更新,watcher被通知

Watcher类的构造函数接收一个函数fn,在实例化时会调用get方法。get方法将当前watcher设置为全局的Dep.target,然后执行fn函数。在执行fn的过程中,如果访问到了响应式数据,defineReactive函数中的get方法就会将当前watcher收集到相应的数据依赖中。当数据发生变化时,Dep类的notify方法会调用watcherupdate方法,从而实现数据更新时的相应操作,比如重新渲染视图。

 

五、嵌套对象的响应式处理

实际开发中,数据往往是复杂的嵌套结构。Vue 巧妙地通过递归方式处理嵌套对象,确保深度嵌套的对象也具备响应式特性。

function defineReactive(obj) {Object.keys(obj).forEach(key => {let val = obj[key];const dep = new Dep();// 递归处理嵌套对象if (typeof val === 'object') {defineReactive(val);}Object.defineProperty(obj, key, {get() {if (Dep.target) {dep.depend();}return val;},set(newVal) {if (newVal!== val) {val = newVal;// 处理嵌套对象的新值if (typeof newVal === 'object') {defineReactive(newVal);}dep.notify();}}});});
}// 测试嵌套对象
const nestedData = {user: {name: 'John',age: 30}
};
defineReactive(nestedData);const watcherNested = new Watcher(() => {console.log('用户姓名: ', nestedData.user.name);
});
nestedData.user.name = 'Mike'; // 数据更新,watcher被通知

在上述代码中,改进后的defineReactive函数会遍历对象的所有属性。对于对象类型的属性,会递归调用自身进行处理,确保每个层级的属性都被劫持并具备响应式能力。当修改嵌套对象的内层属性时,外层的watcher也能及时感知到变化并执行相应的更新操作。

更复杂嵌套对象的响应式示例

const complexData = {company: {departments: [{name: '研发部',employees: [{ name: '张三', age: 28 },{ name: '李四', age: 30 }]},{name: '市场部',employees: [{ name: '王五', age: 26 }]}]}
};function defineReactive(obj) {Object.keys(obj).forEach(key => {let val = obj[key];const dep = new Dep();if (Array.isArray(val)) {val.forEach(item => {if (typeof item === 'object') {defineReactive(item);}});} else if (typeof val === 'object') {defineReactive(val);}Object.defineProperty(obj, key, {get() {if (Dep.target) {dep.depend();}return val;},set(newVal) {if (newVal!== val) {val = newVal;if (Array.isArray(newVal)) {newVal.forEach(item => {if (typeof item === 'object') {defineReactive(item);}});} else if (typeof newVal === 'object') {defineReactive(newVal);}dep.notify();}}});});
}defineReactive(complexData);const watcherComplex = new Watcher(() => {console.log('研发部第一个员工姓名: ', complexData.company.departments[0].employees[0].name);
});complexData.company.departments[0].employees[0].name = '赵六'; // 数据更新,watcher被通知

这个示例展示了在更复杂的嵌套对象(包含数组和多层嵌套对象)情况下,Vue 的响应式原理是如何工作的。通过递归处理,确保了深层数据的变化也能被正确监听和响应。

 

六、总结

通过对 Vue 响应式原理的深入剖析,我们了解到它如何通过数据劫持、依赖收集和发布 - 订阅模式,实现了数据与视图之间的高效同步。这一机制不仅让开发者能够专注于数据的处理和业务逻辑的实现,无需手动繁琐地操作 DOM 来更新视图,还极大地提高了应用的性能和用户体验。

掌握 Vue 的响应式原理,对于开发者来说,就像是掌握了一把打开高效开发大门的钥匙。它不仅有助于我们更好地理解 Vue 的工作机制,在编写代码时能够更加得心应手,编写出更优雅、更高效的应用,还为我们探索其他前端框架的响应式实现提供了宝贵的思路和经验。

希望这篇文章能让你对 Vue 的响应式原理有更深入的理解。如果你在学习和实践过程中有任何疑问或心得,欢迎在评论区留言分享,让我们一起交流进步!

相关文章:

深入剖析 Vue 的响应式原理:构建高效 Web 应用的基石

深入剖析 Vue 的响应式原理&#xff1a;构建高效 Web 应用的基石 在前端开发的广阔天地里&#xff0c;Vue.js 凭借其简洁易用的特性和强大的功能&#xff0c;成为众多开发者的心头好。其中&#xff0c;响应式原理作为 Vue 的核心亮点之一&#xff0c;让数据与视图之间实现了高…...

40.日常算法

1.无重复字符的最长子串 题目来源 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”&#xff0c;所以其长度为 3。 class Solution { public:int lengthOfL…...

CAS单点登录(第7版)11.SSO SLO

如有疑问&#xff0c;请看视频&#xff1a;CAS单点登录&#xff08;第7版&#xff09; SSO & SLO 安装IDEA Download IntelliJ IDEA – The IDE for Professional Development in Java and Kotlin 安装Maven Download Apache Maven – Maven MAVEN_HOMED:\apache-maven…...

Bob the Canadian

1&#xff1a;around the house Hi! Bob the Canadian here! Let’s learn English around the house. Come on in! Hi, Bob the Canadian here. Welcome to this video. If this is your first time here, don’t forget to click the subscribe button below, and give…...

CAS单点登录(第7版)16.模仿

如有疑问&#xff0c;请看视频&#xff1a;CAS单点登录&#xff08;第7版&#xff09; 模仿 概述 代理身份验证 代理身份验证&#xff08;模拟&#xff09;&#xff0c;有时称为 Web 的 sudo&#xff0c;是代表其他用户进行身份验证的能力。 在这种情况下&#xff0c;两个参…...

预留:大数据Hadoop之——部署hadoop+hive+Mysql环境(Linux)

传送门目录 前期准备 一、JDK的安装 1、安装jdk 2、配置Java环境变量 3、加载环境变量 4、进行校验 二、hadoop的集群搭建 1、hadoop的下载安装 2、配置文件设置 2.1. 配置 hadoop-env.sh 2.2. 配置 core-site.xml 2.3. 配置hdfs-site.xml 2.4. 配置 yarn-site.xm…...

RabbitMQ介绍以及基本使用

文章目录 一、什么是消息队列&#xff1f; 二、消息队列的作用&#xff08;优点&#xff09; 1、解耦 2、流量削峰 3、异步 4、顺序性 三、RabbitMQ基本结构 四、RabbitMQ队列模式 1、简单队列模式 2、工作队列模式 3、发布/订阅模式 4、路由模式 5、主题模式 6、…...

C++演示中介模式

避免两个模块之间的耦合&#xff0c;使用中介模式解决。下面是C代码 #include <iostream> #include <vector>using namespace std;class client;//中介 class mediator { public:void addclient(client* client) {clientVec.push_back(client);}void send(const s…...

Vue的简单入门 一

声明&#xff1a;本版块根据B站学习&#xff0c;创建的是vue3项目&#xff0c;用的是vue2语法风格&#xff0c;仅供初学者学习。 目录 一、Vue项目的创建 1.已安装15.0或更高版本的Node.js 2.创建项目 二、 简单认识目录结构 三、模块语法中的指令 1.v-html 1.文本插值…...

【免费送书活动】《MySQL 9从入门到性能优化(视频教学版)》

本博主免费赠送读者3本书&#xff0c;书名为《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;》。 《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 这本书已经公开…...

export default与export区别

1.定义&#xff1a; export default‌&#xff1a;用于导出模块中的默认成员。一个模块中只能有一个export default&#xff0c;通常用于导出模块的主要功能或对象。导入时可以使用任意名称&#xff0c;因为它没有具体的名称‌ ‌export‌&#xff1a;用于导出模块中的多个成…...

最佳的出牌方法

最佳的出牌方法 真题目录: 点击去查看 E 卷 200分题型 题目描述 手上有一副扑克牌,每张牌按牌面数字记分(J=11,Q=12,K=13,没有大小王),出牌时按照以下规则记分: 出单张,记牌面分数,例如出一张2,得分为2出对或3张,记牌面分数总和再x2,例如出3张3,得分为(3+3+3)x2=1…...

Kotlin 2.1.0 入门教程(二十一)数据类

数据类 数据类主要用于存储数据。 对于每个数据类&#xff0c;编译器会自动生成一些额外的成员函数&#xff0c;这些函数支持将实例打印为易读的输出、比较实例、复制实例等操作。 数据类使用 data 关键字标记&#xff1a; data class User(val name: String, val age: Int…...

30天开发操作系统 第 20 天 -- API

前言 大家早上好&#xff0c;今天我们继续努力哦。 昨天我们已经实现了应用程序的运行, 今天我们来实现由应用程序对操作系统功能的调用(即API, 也叫系统调用)。 为什么这样的功能称为“系统调用”(system call)呢&#xff1f;因为它是由应用程序来调用(操作)系统中的功能来完…...

WEB安全--SQL注入--floor报错注入

一、原理&#xff1a; floor()报错注入需要组合count()、rand()、group by()等函数使用&#xff0c;通过一些手段使数据库在处理语句时产生主键重复的报错&#xff0c;从而达到爆出信息的目的 二、内容&#xff1a; ?id-1 or (select 1 from (select count(*),concat(databa…...

【java面向对象的三大特性】封装、继承和多态

目录标题 一、封装&#xff08;Encapsulation&#xff09;&#xff1a;二、继承&#xff08;Inheritance&#xff09;&#xff1a;三、多态&#xff08;Polymorphism&#xff09;&#xff1a;1. 多态的三个必要条件&#xff1a;2.多态的具体实现&#xff1a;3.多态的使用场景&a…...

Hermite 插值

Hermite 插值 不少实际问题不但要求在节点上函数值相等&#xff0c;而且还要求它的导数值相等&#xff0c;甚至要求高阶导数值也相等。满足这种要求的插值多项式就是 Hermite 插值多项式。 下面只讨论函数值与导数值个数相等的情况。设在节点 a ≤ x 0 < x 1 < ⋯ <…...

【推理llm论文精度】DeepSeek-R1:强化学习驱动LLM推理能力飞跃

最近deepseek R1模型大火&#xff0c;正好复习一下他家的技惊四座的论文https://arxiv.org/pdf/2501.12948 近年来&#xff0c;大型语言模型&#xff08;LLM&#xff09;在推理能力上取得了显著进展&#xff0c;但如何进一步有效提升仍然是研究热点。DeepSeek-AI发布了 DeepS…...

arm linux下的中断处理过程。

本文基于ast2600 soc来阐述&#xff0c;内核版本为5.10 1.中断gic初始化 start_kernel() -> init_IRQ() -> irqchip_init() of_irq_init()主要是构建of_intc_desc. 489-514: 从__irqchip_of_table中找到dts node中匹配的of_table(匹配matches->compatible)&#xf…...

C语言:指针详解

C语言&#xff1a;指针详解 1&#xff1a;指针的基本概念1&#xff1a;什么是指针2&#xff1a;为什么要引入指针3&#xff1a;指针的作用4&#xff1a;指针的类型 2&#xff1a;指针的声明与初始化1&#xff1a; 指针的声明2&#xff1a; 指针的初始化 3&#xff1a;指针的操作…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...

ubuntu22.04有线网络无法连接,图标也没了

今天突然无法有线网络无法连接任何设备&#xff0c;并且图标都没了 错误案例 往上一顿搜索&#xff0c;试了很多博客都不行&#xff0c;比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动&#xff0c;重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...