解读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的收集依赖,这显然不是我们想要的,所以在运行这些方法时需要暂停依赖收集
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都是使用同一个高阶函数实现,在数据访问时收集依赖,数据修改时触发依赖重新执行。其中做了很多的操作判断,保证其能够正常运行,例如对数组一些方法的特殊等。

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

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

相关文章

【数学建模】多波束测线问题(持续更新)

多波束测线问题 问题 1建立模型覆盖宽度海水深度重叠长度重叠率 问题二问题三问题四 问题 1 与测线方向垂直的平面和海底坡面的交线构成一条与水平面夹角为 α \alpha α的斜线(如下图),称 α \alpha α为坡度。请建立多波束测深的覆盖宽度及…

Python代码,强化学习,深度学习

python代码编写,Python算法设计,强化学习优化,改进模型,训练模型,测试模型,可视化绘制,代编运行结果,交互多模型改进,预测模型,算法修改,Python包…

Memcached集群管理:构建高可用性缓存系统

Memcached集群管理:构建高可用性缓存系统 目录 引言Memcached简介高可用性缓存系统的需求Memcached集群架构 单点故障与负载均衡数据分片 构建Memcached集群 环境准备配置和部署 高可用性策略 服务器故障处理数据一致性 监控与维护 性能监控日常维护 总结 1. 引言…

【Spring Boot 自定义配置项详解】

文章目录 一、配置文件1. properties配置1.1 创建配置文件1.2 添加配置项1.3 在应用中使用配置项1.4 多环境配置 2. YAML配置2.1 创建配置文件2.2 添加配置项2.3 在应用中使用配置项2.4 多环境配置 二、自定义配置类1. 创建配置类2. 使用配置类 一、配置文件 Spring Boot支持多…

11.斑马纹列表 为没有文本的链接设置样式

斑马纹列表 创建一个背景色交替的条纹列表。 使用 :nth-child(odd) 或 :nth-child(even) 伪类选择器,根据元素在一组兄弟元素中的位置,对匹配的元素应用不同的 background-color。 💡 提示:你可以用它对其他 HTML 元素应用不同的样式,如 <div>、<tr>、<p&g…

利用PyTorch进行模型量化

利用PyTorch进行模型量化 目录 利用PyTorch进行模型量化 一、模型量化概述 1.为什么需要模型量化&#xff1f; 2.模型量化的挑战 二、使用PyTorch进行模型量化 1.PyTorch的量化优势 2.准备工作 3.选择要量化的模型 4.量化前的准备工作 三、PyTorch的量化工具包 1.介…

linux的rm命令是删除到回收站吗?

不会删除到回收站&#xff0c;在 Linux 中&#xff0c;rm 命令用于直接删除文件或目录&#xff0c;而不是将其移至回收站。这与 Windows 系统的回收站机制有所不同。主要原因有以下几个方面&#xff1a; 设计哲学&#xff1a;Linux 设计哲学之一是尽可能简单直接地处理任务。rm…

【小程序开发】TypeError: _this4.getOpenerEventChannel(...).emit is not a function 问题解决

使用uni-appvue2开发微信小程序时遇到一个界面之前传参的问题。想实现的逻辑是界面返回并通知前一个界面刷新。代码如下&#xff1a; GroupManager.getInstance().addGroupRemote(this.createGroupModel(), () > {uni.hideLoading()uni.showToast({icon: "success&quo…

openGauss学习笔记-312 openGauss 数据迁移-MySQL迁移-迁移MySQL数据库至openGauss-概述

文章目录 openGauss学习笔记-312 openGauss 数据迁移-MySQL迁移-迁移MySQL数据库至openGauss-概述312.1 工具部署架构图 openGauss学习笔记-312 openGauss 数据迁移-MySQL迁移-迁移MySQL数据库至openGauss-概述 312.1 工具部署架构图 当前openGauss支持对MySQL迁移服务&#x…

live555搭建实时播放rtsp服务器

live555关于RTSP协议交互流程 live555的核心数据结构值之闭环双向链表 live555 rtsp服务器实战之createNewStreamSource live555搭建实时播放rtsp服务器 live555 rtsp服务器实战之doGetNextFrame live555可以说是rtsp的专项库&#xff0c;既可以搭建rtsp服务器&#xff0c;…

HTTP协议的演进:从HTTP/1.0到HTTP/2.0

随着互联网技术的不断发展&#xff0c;HTTP协议作为Web通信的基础&#xff0c;也经历了从HTTP/1.0到HTTP/1.1再到HTTP/2.0的演进。本文将逐步深入探讨这两个版本的特点、不足以及改进&#xff0c;以帮助我们更好地理解HTTP协议的发展历程。 一、HTTP/1.0的特点与不足 HTTP/1.…

【多任务YOLO】 A-YOLOM: You Only Look at Once for Real-Time and Generic Multi-Task

You Only Look at Once for Real-Time and Generic Multi-Task 论文链接&#xff1a;http://arxiv.org/abs/2310.01641 代码链接&#xff1a;https://github.com/JiayuanWang-JW/YOLOv8-multi-task 一、摘要 高精度、轻量级和实时响应性是实现自动驾驶的三个基本要求。本研究…

Java基础编程500题——HashSet、LinkedHashSet和TreeSet

&#x1f4a5; 该系列属于【Java基础编程500题】专栏&#xff0c;如您需查看Java基础的其他相关题目&#xff0c;请您点击左边的连接 目录 1. 向HashSet中添加元素&#xff0c;并遍历输出。 2. 使用LinkedHashSet保持插入顺序&#xff0c;并遍历输出。 3. 从HashSet中删除一…

多光谱的空间特征和光谱特征Statistics of Real-World Hyperspectral Images

文章目录 Statistics of Real-World Hyperspectral Images1.数据集2.spatial-spectral representation3.Separable Basis Components4.进一步分析5.复现一下5.1.patch的特征和方差和论文近似&#xff0c;5.2 spatial的basis和 spectral的basis 6.coef model7.join model Statis…

如何让主机显示Docker容器的程序界面,同时支持声音播放

系统中如果安装各种应用软件,很容易会因为版本冲刺引发异常。一个好的办法就是用容器来隔离系统环境,确保主机环境不变。对于一些有界面的程序,可以在容器内运行,让其界面显示在主机上。下面以安装和使用视频剪辑软件shotcut为例,介绍实现方案。 docker run -it --privil…

获取磁盘剩余容量-----c++

获取磁盘剩余容量 #include <filesystem>struct DiskSpaceInfo {double total;double free;double available; };DiskSpaceInfo getDiskSpace(const std::string& path) {std::filesystem::space_info si std::filesystem::space(path);DiskSpaceInfo info;info.…

机器学习 - 信息增益

信息增益&#xff08;Information Gain&#xff09; 信息增益是衡量在特征选择过程中一个特征对数据集分类能力提升的指标。在构建决策树&#xff08;如ID3和C4.5算法&#xff09;时&#xff0c;信息增益用于选择最佳的特征来划分数据集。信息增益基于熵的概念&#xff0c;通过…

多视角数据的不确定性估计:全局观的力量

论文标题&#xff1a;Uncertainty Estimation for Multi-view Data: The Power of Seeing the Whole Picture 中文译名&#xff1a;多视角数据的不确定性估计:全局观的力量 原文地址&#xff1a;Uncertainty Estimation for Multi-view Data: The Power of Seeing the Whole …

python用selenium网页模拟时xpath无法定位元素解决方法2

有时我们在使用python selenium xpath时&#xff0c;无法定位元素&#xff0c;红字显示no such element。上一篇文章写了1种情况&#xff0c;是包含iframe的&#xff0c;详见https://blog.csdn.net/Sixth5/article/details/140342929。 本篇写第2种情况&#xff0c;就是xpath定…

类和对象:赋值函数

1.运算符重载 • 当运算符被⽤于类类型的对象时&#xff0c;C语⾔允许我们通过运算符重载的形式指定新的含义。C规定类类型对象使⽤运算符时&#xff0c;必须转换成调⽤对应运算符重载&#xff0c;若没有对应的运算符重载&#xff0c;则会编译报错&#xff1b;&#xff08;运算…