解读vue3源码-响应式篇2
提示:看到我 请让我滚去学习
文章目录
- vue3源码剖析
- reactive
- reactive使用proxy代理一个对象
- 1.首先我们会走isObject(target)判断,我们reactive全家桶仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
- 2.判断是否已经代理,返回对象本身。
- 3.判断在对象的代理对象在weakmap中是否已经存在,是的话返回存储的代理对象
- 4.判断对象是否为可扩展对象
- 5.使用new proxy代理对象
- reactive使用proxy代理的陷阱封装
- get陷阱代码分析
- 1.首先在get方法中局部化isReadonly和isShallow标识,然后走ifelse判断,返回相应的值
- 2.判断是目标对象是否是数组,如果是数组,并且访问的是数组的一些方法,那么返回对应的方法
- 3.判断是否访问对象上的hasOwnProperty属性,返回对象原型上的方法,并收集依赖
- 4.最后如果是内置stmbol或者是不可追踪的key直接返回res,不进行依赖收集
- 5.如果不是只读调用,进行依赖收集触发track
- 6.如果是浅层代理不需要对访问的属行进行深层代理,返回访问属性值
- 7.访问属性若是对象,那么就递归将子元素也变成代理对象
- set陷阱分析
- 1.拿取当前值和旧值,判断是否目标对象是只读对象,若是不做任何处理返回false
- 2.通过hadKey判断操作类型类型是修改旧属性,还是新增属性,在副作用函数触发时做不同处理
- 3.对比新旧值,触发依赖收集
- deleteProperty、has、ownKeys陷阱
- track函数
- trigger函数
vue3源码剖析
vue代码以模块形式放置在packages文件夹下,分模块打包可以使用treesharking,可以在项目中只应用需要的模块,甚至我们可以只使用单一模块实现相应功能,例如我只使用reactive模块实现和拓展响应式数据。(monorepo)
reactive
我们这次学习的响应式相关api都在reactive文件夹中,那么就让我们看看reactive-api在vue中是怎么实现的:
reactive使用proxy代理一个对象
在reactive文件中我们以上4个方法都是使用createReactiveObject高阶函数实现,参入不同的方法。这是因为我们vue官网的reactive、shallowReactive、redonly、shallowReadonly都是使用这个方法实现的,让我们看看这个函数做了什么处理
这个函数传入5个参数
target:目标对象target,
isReadonly:布尔值isReadonly表示是否创建只读对象,
baseHandlers:基础处理器baseHandlers用于普通对象的代理处理,
collectionHandlers:集合处理器collectionHandlers专门用于处理如数组和Map等集合类型的代理
proxyMap:用于存储代理映射的WeakMap
1.首先我们会走isObject(target)判断,我们reactive全家桶仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
因为底层使用proxy代理,proxy只能代理对象,确定目标是否为可被观察的类型,如果代理目标不是对象直接返回目标本身,dev环境控制台warn
2.判断是否已经代理,返回对象本身。
isReadonly:传入参数是否只读
target[ReactiveFlags.IS_REACTIVE]:判断对象是否已经被代理,代理对象会拦截ReactiveFlags中属性(特殊字符串),如果有值说明已被代理
ReactiveFlags是vue在对象上的标识,我们可以在传入目标上直接加上相应属性,会影响数据的绑定,例如:
上图这样会影响reactive的正确执行。
3.判断在对象的代理对象在weakmap中是否已经存在,是的话返回存储的代理对象
4.判断对象是否为可扩展对象
getTargetType函数会根据传入对象返回相应code码
- 1------传入对象是Object或者Array类型
- 2------传入对象是map、set类型
这两者在proxy陷阱中处理方式不同 - 0------传入对象是不可扩展对象,那么就不用代理
Object.isExtensible(value) 方法会返回 true 当:
对象是可扩展的:默认情况下,JavaScript 中的对象是可扩展的,这意味着你可以向它们添加新的属性。如果一个对象没有被冻结(Object.freeze())或密封(Object.seal()),那么 Object.isExtensible(value) 将返回 true。Object.freeze():(不可写,不可配置,可枚举,不可描述) Object.freeze()方法可以冻结一个对象。
对象没有被设置为不可扩展:如果对象在创建后没有通过 Object.preventExtensions() 方法使其变得不可扩展,那么它依然可扩展。
对象不是原始值:Object.isExtensible() 只能用于对象,如果 value 是一个原始值(如字符串、数字、布尔值、null 或 undefined),该方法会抛出错误,因此在这种情况下不会返回 true 或 false。
markRaw()-api在Vue3.0中的作用是标记一个对象,使其永远不会再成为响应式对象。其给对象属性赋值ReactiveFlags.SKIP为true,那么再使用reactive给次对象做响应式时,默认就会识别为不可扩展对象,不会在做响应式代理
5.使用new proxy代理对象
通过targetType === TargetType.COLLECTION判断对象是否为集合类型,走collectionHandlers或者baseHandlers陷阱函数,并将代理对象存储在proxyMap中
到这里我们得到了proxy对象,那么接下来我们看看传入的这个baseHandlers做了什么
reactive使用proxy代理的陷阱封装
baseHandlers即传入函数mutableHandlers
get陷阱代码分析
MutableReactiveHandle对proxy的get、set、deleteProperty、has、ownKeys陷阱做了相应代理(这些方法我们上一篇都介绍过),继承了BaseReactiveHandler公用类,事实上 mutableHandlers,readonlyHandlers,shallowReactiveHandlers, shallowReadonlyHandlers都继承自这个基础类,这个基础类定义了公用的get陷阱,依照我们上一篇实现的简易版本的reactive中,我们猜测陷阱一个做了两件事:1.返回访问值 2.收集依赖,源码如下
1.首先在get方法中局部化isReadonly和isShallow标识,然后走ifelse判断,返回相应的值
这也就是我们上面代理时为什么可以从ReactiveFlags特殊属性做判断,可以看出是在这里对特殊属性做了相应拦截
2.判断是目标对象是否是数组,如果是数组,并且访问的是数组的一些方法,那么返回对应的方法
Vue3中使用arrayInstrumentations对数组的部分方法做了处理,为什么要这么做呢?
这个方法可以分为2部分:
- 1.对includes、indexOf、lasetindexOf方法进行拦截重写,先调用了数组原型方法进项查找,如果没找到将查找对象的原型又查找了一次,为什么这么做?我们来看示例代码
1.
let obj={}
let arr=reactive([obj])
console.log(arr.includes(obj)) ///不重写includes方法输出 false我们在代理对象arr中去查找obj原始数据,但是reactive在代理[obj]也会递归把obj对象进行代理,这样会导致arr中存储的其实是proxy对象,在arr中找obj会找不到,所以要把arr使用toRaw在arr原始数据上找2.
let obj = {a:1}
let obj2= reactive(obj)
let arr = reactive([obj])
console.log(arr.includes(obj2)) ///不重写includes方法输出 false然后如果是这种在arr原型上也是obj原始数据,找代理对象obj2也找不到会进入逻辑res==-1||res==false,将obj2也使用toRaw得到原始数据再次查找一遍。
- 2.对"push", “pop”, “shift”, “unshift”, "splice"方法进行重写,上一篇中我们提到这些方法会隐式的修改数组长度,而这就会触发length的收集依赖,这显然不是我们想要的,所以在运行这些方法时需要暂停依赖收集
例如:在两个独立的effect中修改长度,不仅会读取‘length’还会设置‘lenth’,这样就会溢栈
const arr=reactive([])
// 第一个副作用函数
effect(() => {arr.push(111)})// 第二个副作用函数effect(() => {arr.push(1222)})当然,vue3中还有running状态检测,如果当前effect函数还在运行就不会再次运行,这也使得但是还是会走依赖收集和触发,只是不会再溢栈而已,但是length属性还是会收集当前effect当你再次修改length属性时会再次触发effect。
effect(() => {arr.push(111)})
effect(() => {
arr.push(1222)})
setTimeout(() => {arr.pop()console.log("🚀 ~ setTimeout ~ arr:", arr)
}, 1000);
//🚀 ~ setTimeout ~ arr:[111,1222,111,1222]
3.判断是否访问对象上的hasOwnProperty属性,返回对象原型上的方法,并收集依赖
4.最后如果是内置stmbol或者是不可追踪的key直接返回res,不进行依赖收集
这一步是为了过滤一些特殊的属性,例如原生的Symbol类型的属性,如:Symbol.iterator、Symbol.toStringTag等等,这些属性不需要进行依赖收集,因为它们是内置的,不会改变;还有一些不可追踪的属性,如:proto、__v_isRef、__isVue这些属性也不需要进行依赖收集;
5.如果不是只读调用,进行依赖收集触发track
6.如果是浅层代理不需要对访问的属行进行深层代理,返回访问属性值
### 6.如果是浅层代理不需要### 6.如果访问属性是一个已经使用ref代理的对象对属性值进行.value结构
7.访问属性若是对象,那么就递归将子元素也变成代理对象
set陷阱分析
当我们看完set我们知道它主要做了访问数据返回和依赖收集,那么我们之前实现的set中应该是数据修改和副作用函数触发
1.拿取当前值和旧值,判断是否目标对象是只读对象,若是不做任何处理返回false
2.通过hadKey判断操作类型类型是修改旧属性,还是新增属性,在副作用函数触发时做不同处理
3.对比新旧值,触发依赖收集
(target === toRaw(receiver))此处判断如果目标是原创原型链中的某个上游,则不要触发。
例如
const obj={}
const proto={bar:1}
const child=reactive(obj)
const parent=reactive(proto)
Object.setPrototypeOf(child, parent)
effect(()=>{console.log("🚀 ~ child:", child.bar)})
child.bar = 2
//🚀 ~ child:",1
//🚀 ~ child:",1
//在effect访问child.bar,child不存在就去原型链找,找到parent.bar,但是parent是响应式对象,这样parent.bar和effect就建立联系了,所以第一次依赖收集收集了child.bar和parent.bar。而对对象设置属性,如果对象不存在此属性,就会找到这个对象的原型,触发原型的[set]内部方法,即parent的[set]方法,所以会被拦截到,这样就解释了为什么会触发2次
deleteProperty、has、ownKeys陷阱
deleteProperty、has陷阱都是常规去收集和触发副作用函数,而ownKeys是有些特别的。
ownKeys在对象或数组for…in遍历时触发,而我们遍历重新触发的条件为数组或对象key长度改变,对象变量在get中我们可以清楚的知道我们要获取的是哪个属性,但是ownKeys中并不能,所以我们在track函数传入ITERATE_KEY作为key
const data = [1,2,3,4,{a:111}]const obj = reactive(data)watchEffect(function effectFn111 () {console.log('11111')for (const key in obj) {}})obj.a=6//11111//11111
tigger函数中,对象新增和删除属性都会影响for…in,for…in依赖的对象key为ITERATE_KEY,所以都要重新执行ITERATE_KEY的副作用函数执行,当判断对象新增删除值时都要重新执行key为ITERATE_KEY的副作用函数,即重新运行for…in存在的副作用函数
到此我们reactive使用了new Proxy代理了对象,返回了一个代理对象,实现了对对象属性访问、更改的拦截,那我们在看下track(依赖收集)和trigger函数(依赖触发)
track函数
track函数就如我们上一篇中将对象-对象属性map-efftct副作用函数map关联存储在了targetMap全局的weakMap中,结构我们非常熟悉。
其中值得一提的是在创建dep时使用的是createDep,这个方法如下,这是为了给dep上挂载一个清除自身的函数。例如当我们这个属性的effects依赖为0时,即这个dep没有依赖,那么我们就可以从调用此方法将属性从tagerMap表上面将其删除。
。
当然还有一些其他操作,例如shouldTrack判断是否收集依赖,我们上面重写数组push等方法是会暂停收集,就是pauseTracking函数更改了这个全局属性来暂停依赖收集,resetTracking重新开启收集依赖。还有一些其他参数,和effect函数相关,我们看effect函数时在细说
trigger函数
tigger函数会读取 track函数收集到的,在访问属性上绑定的effect副作用函数加入一个队列中,循环执行,这样当前修改属性所有依赖都会重新执行更新。
当然在其中也会有一些其他操作例如我们上一篇说数组直接修改length属性,会隐式的改变数组内元素,那么就需要修改属性’length’时,对于数组中所有索引大于等于新长度的元素全部进行副作用触发,还有执行元素新增、删除操作时触发ITERATE_KEY(即对象for…in循环)收集的副作用函数。
还有一些其他参数,和effect函数相关,我们看effect函数时在细说
总结:vue3的reactive能够代理对象,reactive、shallowReactive、redonly、shallowReadonly都是使用同一个高阶函数实现,在数据访问时收集依赖,数据修改时触发依赖重新执行。其中做了很多的操作判断,保证其能够正常运行,例如对数组一些方法的特殊等。
相关文章:

解读vue3源码-响应式篇2
提示:看到我 请让我滚去学习 文章目录 vue3源码剖析reactivereactive使用proxy代理一个对象1.首先我们会走isObject(target)判断,我们reactive全家桶仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 str…...

【测开能力提升-fastapi框架】fastapi能力提升 - 中间件与CORS
1. 中间件 1.1 介绍(ChatGPT抄的,大致可以理解) 一种机制,用于在处理请求和响应之前对其进行拦截、处理或修改。中间件可以在应用程序的请求处理管道中插入自定义逻辑,以实现一些通用的功能,如身份验证、…...
centos7安装es及简单使用
为了方便日后查看,简单记录下! 【启动es前,需要调整这个配置文件(/opt/elasticsearch-6.3.0/config/elasticsearch.yml)的两处ip地址,同时访问页面地址的ip:9200时,ip地址也对应修改】 【启动kibana前,需要调整这个配置文件(/opt/kibana-6.3.0/config/k…...
2024年自动驾驶SLAM面试题及答案(更新中)
自动驾驶中的SLAM(Simultaneous Localization and Mapping,即同步定位与地图构建)是关键技术,它能够让车辆在未知环境中进行自主定位和地图建构。秋招来临之际,相信大家都已经在忙碌的准备当中了,尤其是应届…...

HTML零基础自学笔记(上)-7.18
HTML零基础自学笔记(上) 参考:pink老师一、HTML, Javascript, CSS的关系是什么?二、什么是HTML?1、网页,网站的概念2、THML的基本概念3、THML的骨架标签/基本结构标签 三、HTML标签1、THML标签介绍2、常用标签图像标签ÿ…...
数学建模--图论与最短路径
目录 图论与最短路径问题 最短路径问题定义 常用的最短路径算法 Dijkstra算法 Floyd算法 Bellman-Ford算法 SPFA算法 应用实例 结论 延伸 如何在实际应用中优化Dijkstra算法以提高效率? 数据结构优化: 边的优化: 并行计算&…...
FLINK-checkpoint失败原因及处理方式
在 Flink 或其他分布式数据处理系统中,Checkpoint 失败可能由多种原因引起。以下是一些常见的原因: 资源不足: 如果 TaskManager 的内存或磁盘空间不足,可能无法完成状态的快照,导致 Checkpoint 失败。 网络问题&am…...

Hbase映射为Hive外表
作者:振鹭 Hbase对应Hive外表 (背景:在做数据ETL中,可能原始数据在列式存储Hbase中,这个时候,如果我们想清洗数据,可以考虑把Hbase表映射为Hive的外表,然后使用Hive的HQL来清除处理数据) 1. …...
洛谷P1002(过河卒)题解
题目传送门 思路 直接爆搜会TLE,所以考虑进行DP。 由于卒只可以从左边和上面走,所以走到(i,j)的路程总数为从上面走的路程总数加上从左边走的路程总数。我们用dp[i][j]表示从起点走到(i,j)的路程总数,那么状态转移方程为: dp[…...
微信小程序 async-validator 表单验证 第三方包
async-validator 是一个基于 JavaScript 的表单验证库,支持异步验证规则和自定义验证规则 主流的 UI 组件库 Ant-design 和 Element 中的表单验证都是基于 async-validator 使用 async-validator 可以方便地 构建表单中逻辑,使得错误提示信息更加友好和灵…...
马克·扎克伯格解释为何开源AI对开发者有利
Meta 今天发布了 Llama 3.1 系列人工智能模型,在人工智能领域取得了重大进展,其性能可与领先的闭源模型相媲美。值得一提的是,在多项人工智能基准测试中,Llama 3.1 405B 模型的性能超过了 OpenAI 的 GPT-4o 和 Claude 3.5 Sonnet。…...
游戏外挂的技术实现与五年脚本开发经验分享
引言: 在数字娱乐的浪潮中,电子游戏成为许多人生活中不可或缺的一部分。然而,随着游戏的普及,一些玩家为了追求更高效的游戏体验或不正当竞争优势,开始使用游戏外挂程序。这些外挂往往通过修改游戏正常运行机制来提供非…...

认识神经网络【多层感知器数学原理】
文章目录 1、什么是神经网络2、人工神经网络3、多层感知器3.1、输入层3.2、隐藏层3.2.1、隐藏层 13.2.2、隐藏层 2 3.3、输出层3.4、前向传播3.4.1、加权和⭐3.4.2、激活函数 3.5、反向传播3.5.1、计算梯度3.5.2、更新权重和偏置 4、小结 🍃作者介绍:双非…...
MySQL入门学习-SQL高级技巧.CTE和递归查询
在 MySQL 中,SQL 高级技巧包括了 Common Table Expressions(CTE)和递归查询等。 一、CTE(Common Table Expressions,公共表表达式)的概念: CTE 是一个临时的结果集,它可以在一个查询…...

键盘是如何使用中断机制的?当打印一串字符到显示屏上时发生了什么???
当在键盘上按下一个键时会进行一下操作: 1.当按下任意一个键时,键盘编码器监控会来判断按下的键是哪个 2.键盘控制器用将解码,将键盘的数据保存到键盘控制器里数据寄存器里面 3.此时发送一个中断请求给中断控制器,中断控制器获取到中断号发送…...
Spring Boot 接口访问频率限制的实现详解
目录 概述为什么需要接口访问频率限制常见的实现方式 基于过滤器的实现基于拦截器的实现基于第三方库Bucket4j的实现 实际代码示例 基于过滤器实现Rate Limiting基于拦截器实现Rate Limiting使用Bucket4j实现Rate Limiting 最佳实践 选择合适的限流算法优化性能记录日志和监控…...

前端页面:用户交互持续时间跟踪(duration)user-interaction-tracker
引言 在用户至上的时代,精准把握用户行为已成为产品优化的关键。本文将详细介绍 user-interaction-tracker 库,它提供了一种高效的解决方案,用于跟踪用户交互的持续时间,并提升项目埋点的效率。通过本文,你将了解到如…...

中文分词库 jieba 详细使用方法与案例演示
1 前言 jieba 是一个非常流行的中文分词库,具有高效、准确分词的效果。 它支持3种分词模式: 精确模式全模式搜索引擎模式 jieba0.42.1测试环境:python3.10.9 2 三种模式 2.1 精确模式 适应场景:文本分析。 功能࿱…...

EXO-helper解释
目录 helper解释 helper解释 在Python中,字符串 "\033[93m" 是一个ANSI转义序列,用于在支持ANSI转义码的终端或控制台中改变文本的颜色。具体来说,\033[93m 用于将文本颜色设置为亮黄色(或浅黄色,具体取决于终端的显示设置)。 这里的 \033 实际上是八进制的 …...

Qt开发网络嗅探器01
引言 随着互联网的快速发展和普及,人们对网络性能、安全和管理的需求日益增长。在复杂的网络环境中,了解和监控网络中的数据流量、安全事件和性能问题变得至关重要。为了满足这些需求,网络嗅探器作为一种重要的工具被 广泛应用。网络嗅探器是…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...

springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...

数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...

ui框架-文件列表展示
ui框架-文件列表展示 介绍 UI框架的文件列表展示组件,可以展示文件夹,支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项,适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...
深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙
WebGL:在浏览器中解锁3D世界的魔法钥匙 引言:网页的边界正在消失 在数字化浪潮的推动下,网页早已不再是静态信息的展示窗口。如今,我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室,甚至沉浸式的V…...