从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷
系列文章目录
内容 | 链接 |
---|---|
从Vue2到Vue3【零】 | Vue3简介 |
从Vue2到Vue3【一】 | Composition API(第一章) |
从Vue2到Vue3【二】 | Composition API(第二章) |
从Vue2到Vue3【三】 | Composition API(第三章) |
从Vue2到Vue3【四】 | Composition API(第四章) |
从Vue2到Vue3【五】 | 从Vue2到Vue3【五】——新的组件(Fragment、Teleport、Suspense) |
从Vue2到Vue3【六】 | 从Vue2到Vue3【六】——Vue3的改变(文末送书) |
文章目录
- 系列文章目录
- 前言
- 一、Vue2中的响应式原理
- 二、模拟简易版响应式原理
- 三、Vue2响应式数据带来的缺陷
- 3.1 新增属性的响应问题
- 3.2 数组变动的响应问题
- 3.3 对象属性的删除问题
- 四、Vue2响应式缺陷的解决办法
- 4.1 新增属性的响应问题
- 4.2 数组变动的响应问题
- 4.3 对象属性的删除问题
- 总结
前言
Vue是一款流行的JavaScript框架,它以其简洁易用的语法和强大的响应式能力而受到广泛关注。Vue的响应式数据机制是其核心特性之一,它能够自动追踪数据的变化,并实时更新相关的视图。然而,Vue 2中的响应式数据机制并非完美无缺,本文将探讨Vue 2响应式原理及其存在的缺陷。
一、Vue2中的响应式原理
- Vue 2的响应式原理:
在Vue 2中,响应式是通过使用Object.defineProperty()方法来实现的。
在组件实例化过程中,Vue会对数据对象(data)进行递归地遍历,将每个属性都转换为getter/setter,并且为每个属性创建一个依赖追踪的系统。当属性被访问或修改时,getter/setter会触发依赖追踪系统,从而进行依赖收集和派发更新,以保证数据和视图的同步。 - 具体实现步骤如下:
1.创建Observer对象:通过递归地将data对象的属性转换为响应式属性,使用Object.defineProperty()为每个属性添加getter和setter方法。Vue2中 通过使用 Object.defineProperty() 方法,将对象的属性转换成 getter 和 setter,当数据发生变化时,会自动触发相应的更新函数,实现数据的响应式。
2.创建Dep对象:用来管理 Watcher,它用来收集依赖、删除依赖和向依赖发送消息等。用于解耦属性的依赖收集和派发更新操作。
3.创建Watcher对象:Watcher对象用于连接视图和数据之间的桥梁,当被依赖的属性发生变化时,Watcher对象会接收到通知并更新视图。当数据发生变化时,它会通知订阅该数据的组件更新视图。Watcher 在实例化时会将自己添加到 Dep 中,当数据发生变化时,会触发相应的更新函数。
4.模板编译:Vue会解析模板,将模板中的数据绑定指令转译为对应的更新函数,以便在数据发生变化时调用。
在修改对象的值的时候,会触发对应的 setter, setter通知之前依赖收集得到的 Dep 中的Watcher,告诉它自己的值改变了,需要重新渲染视图。这时候这些 Watcher就会开始调用 update 来更新视图, 对应的getter触发追踪,把新值重新渲染到视图上
input用v-model绑定数据,我们需要在input元素上添加事件监听,每当input事件被触发时,就修改对应的data,data里的数据又会响应式更新回视图
二、模拟简易版响应式原理
实现思路:
定义一个Observe构造函数用于对data对象的属性进行数据劫持。我们使用Object.defineProperty()方法对data对象的每个属性进行劫持,定义了属性的getter和setter方法。
在getter方法中,我们返回属性的值。在setter方法中,我们判断新值是否与旧值不同,如果不同,则更新属性的值,并触发依赖更新。
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>Document</title></head><body><script type="text/javascript" >let data = {name:'前端百草阁',age:21,}function Observer(obj){//汇总对象中所有的键形成一个数组const keys = Object.keys(obj)//遍历keys.forEach((k)=>{Object.defineProperty(this,k,{get(){return obj[k]},set(val){console.log(`${k}被改了,我要通知Vue重新去解析模板.....`)obj[k] = val}})})}//创建一个监视的实例对象,用于监视data中属性的变化const obs = new Observer(data) //准备一个vm实例对象let vm = {}vm._data = data = obs</script></body>
</html>
这个时候,原先data里的属性就会各自有一个为他们服务的getter和setter,变成了具有响应式的属性
- 简易式版本的缺陷
- 缺陷一:正常vue中会做一个数据代理,当访问vm.name时,访问的其实是vm._data.name,这样做了数据代理后使用起来更方便
- 缺陷二: 简易式版本没有考虑到data里面的属性值还是对象的情况,在Vue中利用递归的方法将data里所有的属性通过递归的方式都转换为了响应式属性(即使属性值是一个数组,数组里藏了对象,依然可以把对应的属性转换为响应式属性)
- 缺陷一:正常vue中会做一个数据代理,当访问vm.name时,访问的其实是vm._data.name,这样做了数据代理后使用起来更方便
这里有一个小tips,利用this指向obs,访问this(obs)里的属性,getter返回的其实是obj里的属性(数据代理),为什么要这样呢?如果说你访问obj里的属性,我真的通过getter给你返回了obj里对应的属性,返回的obj里的属性又要去触发自己的getter,那是不是就陷入死循环了呢?导致的问题就是无论你是触发getter 还是setter都会导致超出最大调用堆栈这个错误
解决这个问题还有一个办法就是利用闭包,利用闭包把初始值传给value存起来了,后续getter和setter都是针对闭包内的value,和原本的obj隔离开了,当你访问或者设置obj.key的时候,就会去修改对应的val(由于闭包val不会被垃圾机制回收)就不存在最大调用堆栈溢出的情况了
function observe(obj) {if (!obj || typeof obj !== 'object') {return;}Object.keys(obj).forEach(function(key) {defineReactive(obj, key, obj[key]); });
}function defineReactive(obj, key, val) {observe(val); // 递归地对data对象的属性进行数据劫持Object.defineProperty(obj, key, {get: function() {return val;},set: function(newValue) {if (newValue !== val) {val = newValue;// 触发依赖更新updateView();}}});
}function updateView() {document.querySelector('h1').innerText = vm.message;
}// 初始化数据劫持
observe(vm.$data);
在上述代码中,observe函数用于递归地对data对象的属性进行数据劫持。在defineReactive函数中,我们使用Object.defineProperty()方法对data对象的每个属性进行劫持,定义了属性的getter和setter方法。
在getter方法中,我们返回属性的值。在setter方法中,我们判断新值是否与旧值不同,如果不同,则更新属性的值,并触发依赖更新。
最后,我们调用observe(vm.$data)来初始化数据劫持,使得Vue能够捕获到对data对象属性的访问和修改操作,并触发相应的依赖更新。
三、Vue2响应式数据带来的缺陷
Vue 2中的响应式数据存在一些缺陷,但通过使用Vue提供的补救办法,可以解决大部分响应式数据的问题。
3.1 新增属性的响应问题
Vue在初始化时会对data对象的属性进行数据劫持,但是对于后续新增的属性,Vue无法自动进行响应式处理。
Vue 无法探测普通的新增属性 ,比如 this.myObject.saying = 'hi'
,这个新增的saying属性是不具有响应式的,Vue探测不到
3.2 数组变动的响应问题
Vue对数组的变动(例如通过索引修改数组元素、通过splice方法删除或插入元素)无法直接进行响应式处理。
例如此时在data里定义了这些数据
data:{friends:[{name:'jerry',age:35},{name:'tony',age:36},'前端百草阁']}
不难发现数组中的对象都是响应式的,但数组中的普通元素却不是响应式的,意味着若直接修改数组中的元素Vue无法监测到
如果你通过数组下标修改对象属性的话是可以监测的,因为对象里的属性都是响应式的,但如果你通过数组下标修改普通元素是无法监测到的
如果用一个新数组覆盖掉原先的数组,Vue是能监测到的
3.3 对象属性的删除问题
Vue无法直接检测到对象属性的删除操作。
利用delete删除对象的属性,无法被Vue监测到
四、Vue2响应式缺陷的解决办法
4.1 新增属性的响应问题
Vue.set( target, propertyName/index, value )
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = ‘hi’)
给data中的student对象添加一个属性,并且是响应式的,有两种写法,Vue.set或者this.$set
// Vue.set(this._data.student,'sex','男') // 这里加不加_data实际上都可以,就是一个数据代理,访问谁都一样,那我们肯定选择偷懒啦
this.$set(this.student,'sex','男') // this代表vm vue实例对象
实现了新增了student对象里的sex属性,并且该属性有为自己服务的getter、setter(具有响应式)
但是,Vue官网明确指出:注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
简单来说就是,set方法的第一个参数target不允许 是vm(vue实例)、也不允许是vm._data(根数据对象)
4.2 数组变动的响应问题
第一中解决办法,使用数组变异方法:Vue提供了一些数组变异方法(例如push、pop、shift、unshift、splice、sort和reverse),这些方法会触发数组的响应式更新。
如果不是这七个方法的话,比如调用slice等数组方法的话,记得要把返回的新数组覆盖掉原来的旧数组,依然能触发响应式
第二种解决办法,利用set方法,set方法不但能解决对象新增属性的问题,还能解决修改数组的问题(用的不多)
4.3 对象属性的删除问题
Vue.delete方法:用来删除对象的属性,并触发响应式更新。例如,可以使用Vue.delete(vm.someObject, ‘propertyToDelete’)来删除一个属性。
正常的delete方法,虽然确实删除了属性,但是无法被监测到
利用Vue.delete完美解决删除对象属性无法被监测的问题(很少用到),或者vm.$delete(vm.person,'name')
总结
Vue 2的响应式数据机制在大多数情况下能够满足我们的需求,但也存在一些缺陷。
首先,Vue无法直接响应新增的属性,需要使用特定的方法进行补救。其次,对于数组的变动和对象属性的删除,Vue也无法直接进行响应式处理,需要使用相应的方法来触发更新。这些缺陷在实际开发中可能会带来一些困扰。
但幸运的是,Vue提供了一些补救的办法,如Vue.set和Vue.delete方法,以及数组变异方法。通过这些补救措施,我们可以弥补Vue 2响应式数据机制的不足,提升开发效率和用户体验。尽管如此,我们也期待Vue未来版本的改进,在响应式数据方面能够更加智能和灵活,以满足更多复杂场景的需求。
相关文章:

从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷
系列文章目录 内容链接从Vue2到Vue3【零】Vue3简介从Vue2到Vue3【一】Composition API(第一章)从Vue2到Vue3【二】Composition API(第二章)从Vue2到Vue3【三】Composition API(第三章)从Vue2到Vue3【四】C…...

用C语言实现堆排序算法
1.设计思路 排序的思想将一个数组按递增的顺序进行排序,将数组的第一个位置空下(下标为0),因为会导致子节点和本身同一个结点(i和2i一致),每次堆排序在下标1的位置放上了最大值,然后…...

tauri在github上进行自动更新打包并发版过程,实战操作避坑
从网上找了很多很多的文章,结果还是入坑了,一个问题找了一天才解决: Error A public key has been found, but no private key. Make sure to set TAURI_PRIVATE_KEY environment variable. 596 ELIFECYCLE Command failed with exit code…...

css中flex后文本溢出的问题
原因: 为了给flex item提供一个合理的默认最小尺寸,flex将flex item的min-width 和 min-height属性设置为了auto flex item的默认设置为: min-width: auto 水平flex布局 min-height:auto 垂直flex布局 解决办法&…...
restful接口设计规范[仅供参考]
1. 域名 应该尽量将API部署在专用域名之下。 https://api.example.com 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。 https://www.example.org/api/2. 版本(Versioning) 应该将API的版本号放入URL。 http://…...

Metabase 远程代码执行(CVE-2023-38646)
漏洞描述 Metabase是一款开源数据分析及可视化工具。它可允许用户连接至各种不同类型数据源,未经身份认证的攻击者可利用本漏洞在服务器上以运行 Metabase服务器的权限进行任意命令执行。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩…...

【TiDB理论知识 07】SQL执行流程
一 DML语句读写流程 1 DML语句读流程概要 用户发出SQL 被协议层接收 Protocal Layer 通过PD获取时间戳 parse模块 解析SQL,通过词法解析 与 语法解析 生成AST语法树 编译SQL Compile模块 ,区分点查 与 非点查,生成执行计划 发送给Executor,从TIKV获…...

微服务——服务异步通讯RabbitMQ
前置文章 消息队列——RabbitMQ基本概念容器化部署和简单工作模式程序_北岭山脚鼠鼠的博客-CSDN博客 消息队列——rabbitmq的不同工作模式_北岭山脚鼠鼠的博客-CSDN博客 消息队列——spring和springboot整合rabbitmq_北岭山脚鼠鼠的博客-CSDN博客 目录 Work queues 工作队列…...
事件冒泡、事件捕获和事件委托
原文合集地址如下,有需要的朋友可以关注 本文地址 合集地址 什么是事件冒泡、事件捕获和事件委托? 事件冒泡(Event Bubbling)、事件捕获(Event Capturing)和事件委托(Event Delegation&…...
WEB 典型安全功能说明
WEB 典型安全功能 认证Authentication 认证是指通过验证用户的身份来确认用户是否有权访问某个系统或资源。在Web安全中,认证是非常重要的一环,它可以防止未经授权的访问,保护用户的数据和系统的安全。 登录 登录是用户认证的常见方式之一…...

SQL编译优化原理
最近在团队的OLAP引擎上做了一些SQL编译优化的工作,整理到了语雀上,也顺便发在博客上了。SQL编译优化理论并不复杂,只需要掌握一些关系代数的基础就比较好理解;比较困难的在于reorder算法部分。 文章目录 基础概念关系代数等价 j…...
qt signal slots lambda
这里用到了qt的版本检测 连接 Combox的currentIndexChanged事件 emit来触发处理的事件 ,进行业务或逻辑处理 这样的写法是lambda表达式的写法,和c#中的 (obj)>{ //todo } 类同 [](int indx){ //todo } #if QT_VERSION > QT_VERSION_CHECK(5,7,0)c…...
Spring【声明式事务】
事务简介 把一组业务当成一个业务来做;要么都成功,要么都失败!事务在项目开发中,十分重要,涉及到数据一致性的问题,需要十分注意!确保完整性和一致性! 事务的ACID原则:…...

【雕爷学编程】MicroPython动手做(17)——掌控板之触摸引脚2
知识点:什么是掌控板? 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片,支持WiFi和蓝牙双模通信,可作为物联网节点,实现物联网应用。同时掌控板上集成了OLED…...

pytorch 中 view 和reshape的区别
在 PyTorch(一个流行的深度学习框架)中, reshape 和 view 都是用于改变张量(tensor)形状的方法,但它们在实现方式和使用上有一些区别。下面是它们之间的主要区别: 实现方式: reshap…...

认识数组指针
文章目录 数组指针的定义数组指针的应用 数组指针的定义 类比 整形数组——存放整形的数组 指针数组——存放指针的数组 整形指针——存放整形地址的指针 数组指针——存放数组地址的指针 深度理解 在之前我们知道:数组名表示首元素地址,但是有…...

SSM面试题-Spring容器的启动流程
解答: 1. BeanDefinitionReader读取配置文件(xml yml properties),创建BeanDefinition(存储bean的定义信息) 2. 配置文件读取成功后,将相应的配置转换成 BeanDefinition 的对象实例保存在DefaultListableBeanFactory#beanDefinitionMap 中 3. 根据配置的 BeanFacto…...
Vue 3:玩一下web前端技术(八)
前言 本章内容为VUE基础与相关技术讨论。 上一篇文章地址: Vue 3:玩一下web前端技术(七)_Lion King的博客-CSDN博客 下一篇文章地址: (暂无) 一、基础 官方文档:创建一个 Vue…...

AI绘画Stable Diffusion原理之Autoencoder-Latent
前言 传送门: stable diffusion:Git|论文 stable-diffusion-webui:Git Google Colab Notebook:Git kaggle Notebook:Git 今年AIGC实在是太火了,让人大呼许多职业即将消失,比如既能帮…...
C++核心知识点总结
学习一门新的程序设计语言得到最好方法就是练习编写程序! C基础 变量和基本类型 基本内置类型 定义解释 算术类型 整型:包括字符和布尔类型,bool、char、wchar_t、char16_t、char32_t、short、int、long、long long、 浮点型:…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...