Vue3源码解析--收集的依赖是什么?怎么收集的?什么时候收集的?
从Vue开始较大范围在前端应用开始,关于Vue一些基础知识的讨论和面试问题就在开发圈子里基本上就跟前几年的股票和基金一样,楼下摆摊卖酱香饼的阿姨都能说上几句那种。找过前端开发工作或者正在找开发工作的前端都知道,面试官基本上都有那么几个常问的问题,而网上呢也有那么一套可以用来背诵的“八股文”,自己懂多少没有关系,应付面试官还是够的,可以算是屡试不爽吧。
背诵面试八股文无可厚非的,可以说是每一个找工作的人都干过和必须干的事情,因为我们都要工作,都要恰饭。只有恰上饭,才能去谈些伟大的理想。背“八股文”本是一种捷径,尤其是本身对一门技术不是特别了解的开发者,就是那种刚刚能使用它那种。
在众多关于Vue的面试“八股文”中,今天讲的是其中最常问的一个--Vue中的依赖收集。本文也将从代码层面,讲清楚关于依赖收集的几个问题。
- 收集的依赖是什么?(what)
- 怎么收集的依赖? (how)
- 什么时候收集? (when)
至于为什么要收集依赖(why),现在就可以先告诉答案。收集依赖,其核心作用是在数据发生变化的时候可以做出相应的动作,比如刷新视图,为了执行这一动作,我们就得知道是谁在什么时候发生了变化,所以我们要收集依赖。
下面我们结合代码,尽可能通俗的讲解关于上述的三个问题:
在搞清楚依赖收集之前,先把源码中几个概念性的东西说明一下,建议下载Vue3源码进行对照着看:
Dep: 本质上是一个Map实例,同时在map实例上绑定一个celanup函数和一个computed属性。
ReactiveEffect: 相当于2.x版本中的Watcher类, 里头有一个deps数组,用来存dep, 每个实例里面都有一个track_id用来标识唯一性。
effect函数: 里头实例化一个ReactiveEffect对象,同时绑定一些options, 返回值是一个runner,实际上是对ReactiveEffect对象行为的一种业务封装。
下面以一行简单的代码开始关于依赖收集的探索。
const num = ref(1);
// packages/reactivity/src/ref.ts
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
return createRef(value, false)
}
ref函数主要是对createRef做了一个函数包装,主要内容看到createRef函数。
// packages/reactivity/src/ref.ts
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
createRef函数在这里对原始数据rawValue做了一个判断,如果数据本身就是响应式数据了,就直接返回它本身,如果不是,就返回一个实例化的RefImpl对象。
// packages/reactivity/src/ref.ts
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(
value: T,
public readonly __v_isShallow: boolean,
) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, DirtyLevels.Dirty, newVal)
}
}
}
重点来了,RefImple类里头,才是真正包含了从原始数据变成响应式数据,以及收集依赖的逻辑。在一个refImpl实例中,里面有一个dep对象,初始值是undefined, 这个dep会这trackRefValue函数执行的过程中被赋值。
下面代码从17-21(get value())行,就是依赖收集的过程:当一个ref型响应式数据通过.value访问时,会触发RefImpl实例中的getter。它会首先执行一个trackValue函数,然后再返回_value值,所以接下来重点看关注trackValue函数,所以依赖是在数据被访问的时候触发的。
// packages/reactivity/src/ref.ts
export function trackRefValue(ref: RefBase<any>) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
trackEffect(
activeEffect,
(ref.dep ??= createDep(
() => (ref.dep = undefined),
ref instanceof ComputedRefImpl ? ref : undefined,
)),
__DEV__
? {
target: ref,
type: TrackOpTypes.GET,
key: 'value',
}
: void 0,
)
}
}u
trackRefValue函数中有两个变量,shouldTrack和activeEffect,暂时我们不去理会它们,只要知道shouldTrack是一个布尔值,activeEffect是一个RectiveEffect实例。
在shouldTrack值为true且activeEffect有值的情况下,首先会将ref转成原始值,然后再执行trackEffect函数。
在执行trackEffect函数的中,第一个是activeEffect, 在任意时刻它在全局是具有唯一性的;第二个是ref.dep, 其中给ref.dep的赋值函数createDep返回一个Dep实例,前面说过的,本质是个map; 第三个函数是个对象,是关于开发环境下debug的一些配置。
在这里,我们可以看到,之前说个的ref实例中原来是undefined的ref.dep赋值,就在此处。
// packages/reactivity/src/effect.ts
export function trackEffect(
effect: ReactiveEffect,
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
if (dep.get(effect) !== effect._trackId) {
dep.set(effect, effect._trackId)
const oldDep = effect.deps[effect._depsLength]
if (oldDep !== dep) {
if (oldDep) {
cleanupDepEffect(oldDep, effect)
}
effect.deps[effect._depsLength++] = dep
} else {
effect._depsLength++
}
if (__DEV__) {
effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!))
}
}
}
trackEffect函数绝对是依赖收集重头戏中的重头戏。
首先上来就是一个判断,dep, 也就是ref中的dep,本质是个map,判断里面是否存在对应的effect, 如果没有,就执行接下来的操作。
dep将effect也就是activeEffect作为键,其_trackId作为值添加到dep,所以我们说的收集的依赖指的就是effect对象。同时我们得到了一个关于dep和effect之间的第一关系,即一个dep可以对应多个effect。
接着,将effects实例中deps数组中最后一个值取出来与当前的dep值进行比对,看是否是同一个值如果不是同一个值,而且oldDep是有值的,那么就执行cleanupDepEffect操作。如果oldDep为空值,就跳过这一步,直接往effect.deps中添加dep。因此,我们在这里得到了关于dep和effect第二个结论,一个effect可以对应多个dep。
代码还有一部分,接着往下看,在oldDep不等于当前dep的时候,直接对effec_depsLength进行加操作,也就是说,effect.deps值没有变,但是_depsLength值却超出了deps数组边界的情况,这也就是为什么上面要判断oldDep是否存在的原因。
由上面上面两个结论我们可以得出,一个dep中可以对应多个effect, 一个effect也可以对应多个dep, 因此dep和effect的关系是多对多的关系。
总结
- 收集的依赖是什么?(what)
我们常说的收集的依赖是effect对象
- 怎么收集的依赖? (how)
判断当前数据dep中有没有activeEffct, 没有就加进去。把大象关进冰箱里要几步!!!
- 什么时候收集? (when)
在数据被访问时,触发getter,进行依赖收集
Vue3源码解析--收集的依赖是什么?怎么收集的?什么时候收集的?的更多相关文章
- 【spring-boot 源码解析】spring-boot 依赖管理梳理图
在文章 [spring-boot 源码解析]spring-boot 依赖管理 中,我梳理了 spring-boot-build.spring-boot-parent.spring-boot-depen ...
- .NET Core实战项目之CMS 第三章 入门篇-源码解析配置文件及依赖注入
作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9998021.html 写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着 ...
- 【spring-boot 源码解析】spring-boot 依赖管理
关键词:spring-boot 依赖管理.spring-boot-dependencies.spring-boot-parent 问题 maven 工程,依赖管理是非常基本又非常重要的功能,现在的工程 ...
- Vue3源码解析(computed-计算属性)
作者:秦志英 前言 上一篇文章中我们分析了Vue3响应式的整个流程,本篇文章我们将分析Vue3中的computed计算属性是如何实现的. 在Vue2中我们已经对计算属性了解的很清楚了,在Vue3中提供 ...
- seajs1.3.0源码解析之module依赖有序加载
/** * The core of loader */ ;(function(seajs, util, config) { // 模块缓存 var cachedModules = {} // 接口修改 ...
- Spring源码解析-基于注解依赖注入
在spring2.5版本提供了注解的依赖注入功能,可以减少对xml配置. 主要使用的是 AnnotationConfigApplicationContext: 一个注解配置上下文 AutowiredA ...
- 【源码解析】凭什么?spring boot 一个 jar 就能开发 web 项目
问题 为什么开发web项目,spring-boot-starter-web 一个jar就搞定了?这个jar做了什么? 通过 spring-boot 工程可以看到所有开箱即用的的引导模块 spring- ...
- vue3源码node的问题
下载vue3源码后,下载依赖时,node的版本需要在10.0.0以上,并且不同的vue3里面的插件的配置对版本依赖还不同,14.0.0以上的版本基本都不支持win7了, win7系统可以安装12.0. ...
- Maven 依赖调解源码解析(三):传递依赖,路径最近者优先
本文是系列文章<Maven 源码解析:依赖调解是如何实现的?>第三篇,主要介绍依赖调解的第一条原则:传递依赖,路径最近者优先.本篇内容较多,也是开始源码分析的第一篇,请务必仔细阅读,否则后 ...
- 简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析
简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析 虽然经常用 OAuth 2.0,但是原理却不曾了解,印象里觉得很简单,请求跳来跳去,今天看完相关介绍,就来捋一捋 ...
随机推荐
- 大数据之路Week10_day05 (Redis的安装与简单命令使用)
Redis 支持单机版和集群,下面的步骤是单机版安装步骤 redis3.0.0版本的安装包百度云链接: 链接:https://pan.baidu.com/s/1mb_SdU5hHlrmUkWN7Drx ...
- winform 实现太阳,地球,月球 运作规律https://www.cnblogs.com/axing/p/18762710
无图眼吊(动图) 缘由 最近我太太在考公学习,给我出了两道高中地理知识的题目,把我问的一头雾水,题目是这样的 第一题 第二题 看到这两道题,当时大脑飞速运转,差点整个身体都在自转了,所以产生了个 ...
- MySQL 8.0下 200GB大表备份,利用传输表空间解决停服发版表备份问题
MySQL 8.0下 200GB大表备份,利用传输表空间解决停服发版表备份问题 问题背景 在停服发版更新的时候,需要预先对一个业务表进行备份,该业务表是200GB大小的表,大概200亿行数据. 因为 ...
- Ubuntu下如何管理多个ssh密钥
Ubuntu下如何管理多个ssh密钥 前言 我一直在逃避这个问题,误以为我能够单纯地用一个 ssh 走天下. 好吧,现实是我不得不管理多个 ssh 做,那就写个博客总结一下吧. 查阅后发现前人已经 ...
- python实现排列组合--itertools
这是一个python自带的工具集,简单好用功能强大,能够大大提升编写代码效率. 功能不止排列组合,其他的用用加深理解了再整理. 官方文档:https://docs.python.org/zh-cn/3 ...
- Linux下如何重启Oracle
操作步骤 切换到oracle用户 su – oracle 通过sqlplus以管理员身份登录 sqlplus / as sysdba 然后执行 shutdown immediate 退出sqlplus ...
- 一步一步教你部署ktransformers,大内存单显卡用上Deepseek-R1
环境准备 硬件环境 CPU:intel四代至强及以上,AMD参考同时期产品 内存:800GB以上,内存性能越强越好,建议DDR5起步 显卡:Nvidia显卡,单卡显存至少24GB(用T4-16GB显卡 ...
- cxDBTreeList:最简单的节点图标添加方法
先在窗体上放ImageList关联到cxDBTreeList,在cxDBTreeList的GetNodeImageIndex事件中写如下: procedure cxDBTreeList1GetNode ...
- Oracle AI应用的LLM模型典型配置
最近在做一些基于Oracle的一些AI应用测试工作,AI肯定离不开配置LLM相关,虽然是简单配置类,但实际还是遇到一些卡点,记录下来供今后参考. 1.配置Embedding模型 2.特殊语法传参JSO ...
- 区块链特辑——solidity语言基础(一)
Solidity语法基础学习 一.智能合约的结构: 首先以上是智能合约的结构,包含版权宣告.编译指示.Using for 宣告.错误定义.输入.列举与枚举.常数.合约.函数.注释.第一个注释不同于其他 ...