解读vue3源码-响应式篇2

news2024/12/23 10:06:57

提示:看到我 请让我滚去学习

文章目录

  • 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.coloradmin.cn/o/1937559.html

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

相关文章

月之暗面科技有限公司(Moonshot AI)内推

月之暗面科技有限公司(Moonshot AI)内推 公司简介 月之暗面科技有限公司,一家致力于实现通用人工智能(AGI)的创新企业,其主力产品 Kimi 智能助手,以其超长文本处理能力和多端同步功能&#xff…

215. 数组中的第K个最大元素 347. 前 K 个高频元素(LeetCode热题100)

215. 数组中的第K个最大元素 - 力扣&#xff08;LeetCode&#xff09; 写个快排&#xff0c;使数组升序&#xff0c;返回倒数第k个元素即可 func quickSort(nums []int, l int, r int) {if l > r {return}x : nums[(l r) / 2]i : l - 1j : r 1for i < j {for {iif n…

【HTML入门】第十六课 - 网页中的按钮们

这一小节&#xff0c;我们说一说 html网页 中的按钮。按钮的作用&#xff0c;就是让用户点呗&#xff0c;点了以后触发一些事件&#xff0c;可以做一些事情。 目录 1 input 按钮 一 2 input 按钮二 3 button 按钮 4 链接a标签 5 用其他元素做按钮 6 用图片做一个按钮 1 …

为什么要从C语言开始编程

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C语言的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;很多小伙伴在入门编程时。都…

Python学习—open函数,json与pickle知识点,Os模块详解

目录 1. Open函数 2.json与pickle模块 json模块 1. json.dumps() 2. json.dump() 3. json.loads() 4. json.load() pickle 模块 1. pickle.dumps() 2. pickle.dump() 3. pickle.loads() 4. pickle.load() 3.Os模块 1. Open函数 在Python中&#xff0c;open() 函数…

【大数据】JSON文件解析,对其文本聚类/情感分析

目录 引言 JSON&#xff08;JavaScript Object Notation&#xff09; 文本聚类K-means 基本步骤 优点 缺点 实际应用 情感分析 核心任务与应用场景 算法原理与技术 json数据集 情感分析实现 文本聚类实现 引言 JSON&#xff08;JavaScript Object Notation&#…

AIoTedge 智能边缘物联网平台

AIoTedge智能边缘物联网平台是一个创新的边云协同架构&#xff0c;它为智能设备和系统提供了强大的数据处理和智能决策能力。这个平台的核心优势在于其边云协同架构设计&#xff0c;它优化了数据处理速度&#xff0c;提高了系统的可靠性和灵活性&#xff0c;适用于多种场景&…

华为OD机考题(HJ61 放苹果)

前言 经过前期的数据结构和算法学习&#xff0c;开始以OD机考题作为练习题&#xff0c;继续加强下熟练程度。 描述 把m个同样的苹果放在n个同样的盘子里&#xff0c;允许有的盘子空着不放&#xff0c;问共有多少种不同的分法&#xff1f; 注意&#xff1a;如果有7个苹果和3…

uniapp bug解决:uniapp文件查找失败:‘uview-ui‘ at main.js:14

文章目录 报错内容解决方法main.js 文件中 uView 主 JS 库引入 uView 的全局 SCSS 主题文件内容修改引入 uView 基础样式内容修改配置 easycom 内容修改 报错内容 10:50:51.795 文件查找失败&#xff1a;uview-ui at main.js:14 10:59:39.570 正在差量编译... 10:59:43.213 文…

【Jmeter】记录一次Jmeter实战测试

Jmeter实战 1、需求2、实现2.1、新建线程组2.2、导入参数2.3、新建HTTP请求2.4、添加监听器2.5、结果 1、需求 查询某个接口在高并发场景下的响应时间(loadtime)&#xff0c;需求需要响应在50ms以内&#xff0c;接下来用Jmeter测试一下 Jmeter安装见文章《Jemeter安装教程&am…

[集成学习]基于python的Stacking分类模型的客户购买意愿分类预测

1 导入必要的库 import pandas as pd import numpy as np import missingno as msno import matplotlib.pyplot as plt from matplotlib import rcParams import seaborn as sns from sklearn.metrics import roc_curve, auc from sklearn.linear_model import LogisticRegres…

C++ | Leetcode C++题解之第240题搜索二维矩阵II

题目&#xff1a; 题解&#xff1a; class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int m matrix.size(), n matrix[0].size();int x 0, y n - 1;while (x < m && y > 0) {if (matrix[x][y] targ…

Linux--实现线程池(万字详解)

目录 1.概念 2.封装原生线程方便使用 3.线程池工作日志 4.线程池需要处理的任务 5.进程池的实现 6.线程池运行测试 7.优化线程池&#xff08;单例模式 &#xff09; 单例模式概念 优化后的代码 8.测试单例模式 1.概念 线程池:* 一种线程使用模式。线程过多会带来调度…

QT--事件(丰富操作,高级功能)

一、事件 1.事件与信号的区别 事件来自外部&#xff0c;是随机发生的。信号来自内部&#xff0c;是主动发生的。有点像外中断和内中断的区别。事件&#xff1a;适用于处理系统级别的输入和状态变化&#xff0c;种类繁多&#xff0c;能够应对复杂的交互需求。信号/槽&#xff…

学习并测试SqlSugar的单库事务功能

SqlSugar支持单库事务、多租户事务、多库事务&#xff0c;本文学习并测试单库事务的基本用法。   使用SqlSugarClient类、ISqlSugarClient接口都可以创建SqlSugarClient数据库操作实例&#xff0c;其区别在于&#xff0c;针对单库而言&#xff0c;SqlSugarClient类支持调用Be…

硬件开发笔记(二十七):AD21导入DC座子原理图库、封装库,然后单独下载其3D模型融合为3D封装

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140541464 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

睿考网:2024年城乡规划师考试时间

2024年城乡规划师报名时间已经结束&#xff0c;考试时间以及考试科目为&#xff1a; 2024年9月7日&#xff1a; 9.00-11.30&#xff0c;城乡规划原理(客观题) 14.00-16.30&#xff0c;城乡规划相关知识(客观题) 2024年9月8日&#xff1a; 9.00-11.30&#xff0c;城乡规划管…

AV1技术学习:Intra Prediction

对于帧内预测模式编码块&#xff0c;亮度分量的预测模式和色度分量的预测模式在比特流中分别发出信号。亮度预测模式是基于相邻左侧和上侧两个编码块预测上下文的概率模型进行熵编码的。色度预测模式的熵编码取决于色度预测模式的状态。帧内预测以变换块为单位&#xff0c;并使…

PyTorch张量拼接方式【附维度拼接/叠加的数学推导】

文章目录 1、简介2、torch.cat3、torch.stack4、数学过程4.1、维度拼接4.1.1、二维张量4.1.2、三维张量4.1.3、具体实例 4.2、维度叠加4.2.1、0维叠加4.2.2、1维叠加4.2.3、2维叠加&#xff08;非常重要⭐&#xff09; &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专…

Android14之调试广播实例(二百二十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…