【React源码 - Fiber架构之Reconciler】

前言

React16架构可以分为三层也是最核心的三个功能分别是:

  • Scheduler(调度器)—调度任务的优先级,高优任务优先进入Reconciler(16新增)
  • Reconciler(协调器)—负责找出变化的组件
  • Renderer(渲染器)— 负责将变化的组件渲染到页面上

本文主要是分析一下Reconciler协调器的理念以及流程。简单就是一句话:Reconciler协调器中主要功能就是使用循环实现可中断递归,并进行Fiber节点的对比,然后打上标记,然后通知Renderer更新

背景

在React中可以通过this.setState、useState、this.forceUpdate、ReactDOM.render等API触发更新,在Reconciler中,mount的组件会调用mountComponent ,update的组件会调用updateComponent 。这两个方法都会递归更新子组件。15版本的时候,是使用递归的方式来遍历Diff对比虚拟DOM的差异然后通知Renderer来进行渲染的,主要是如下功能:

  1. 调用函数组件、或class组件的render方法,将返回的JSX转化为虚拟DOM
  2. 将虚拟DOM和上次更新时的虚拟DOM对比,找出本次更新中变化的虚拟DOM
  3. 通知Renderer将变化的虚拟DOM渲染到页面上

由于递归一旦开始就不可中断,如果diff的层级较多的时候,就会出现递归时间超过16ms,导致页面卡顿。以及在ReReconciler和Renderer是交替工作的。如下图:当第一个li在页面上已经变化后,第二个li再进入Reconciler。
在这里插入图片描述由于整个过程都是同步的,所以在用户看来所有DOM是同时更新的
为了解决这些问题,React决定重写这块架构(Fiber架构),用异步可中断的更新来代替同步更新。使用循环的方式来代替递归实现可中断,从代码可以看出,每次循环都会调用shouldYield判断当前是否有剩余时间。

/** @noinline */
function  workLoopConcurrent() {// Perform work until Scheduler asks us to yieldwhile (workInProgress !== null && !shouldYield()) {workInProgress = performUnitOfWork(workInProgress);}
}

而且Reconciler与Renderer不再是交替工作。当Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟DOM打上代表增/删/更新的标记(使用二进制),类似这样:

export const Placement = /* 插入      */ 0b0000000000010;                 
export const Update = /*    更新      */ 0b0000000000100;
export const PlacementAndUpdate = /*插入并更新*/ 0b0000000000110;
export const Deletion = /* 删除       */ 0b0000000001000;

整个Scheduler与Reconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer。所以即使Scheduler和Reconciler是异步可中断的,但是用户也不会看到未完全更新数据,因为当处理完了之后,统一通知Renderer更新,而Renderer是同步不可中断更新的。
在这里插入图片描述

接下来解释一下待会儿会提到的一些术语:

  • Reconciler工作的阶段被称为render阶段。因为在该阶段会调用组件的render方法。
  • Renderer工作的阶段被称为commit阶段。就像你完成一个需求的编码后执行git commit提交代码。commit阶段会把render阶段提交的信息渲染在页面上。
  • render与commit阶段统称为work,即React在工作中。相对应的,如果任务正在Scheduler内调度,就不属于work。

双缓存树

都知道React在Fiber架构中使用了双缓存,所以开始之前我们先要了解一下什么是双缓存树。

当我们用canvas绘制动画,每一帧绘制前都会调用ctx.clearRect清除上一帧的画面。如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。这种在内存中构建并直接替换的技术叫做双缓存 。

简单来说,双缓存树就是使用两个树来避免更新时的闪烁问题的解决策略,一棵Current Tree(视图中的),一个WorkInProgress Tree(内存中的),这两颗树是可以通过修改alternate指向来互相转化的,当需要更新时,直接用缓存树替换视图树。React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。

双缓存Fiber树

在React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树。current Fiber树中的Fiber节点被称为current fiber,workInProgress Fiber树中的Fiber节点被称为workInProgress fiber,他们通过alternate属性连接。

currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;

React应用的根节点通过使current指针在不同Fiber树的rootFiber间切换来完成current Fiber树指向的切换。即当workInProgress Fiber树构建完成交给Renderer渲染在页面上后,应用根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树就变为current Fiber树。每次状态更新都会产生新的workInProgress Fiber树,通过current与workInProgress的替换,完成DOM更新。

接下来我们以具体例子讲解mount时、update时的构建/替换流程。

Mount时

以下面代码为例:

function  App() {const [num, add] = useState(0);return (<p onClick={() => add(num + 1)}>{num}</p>)
}ReactDOM.render(<App/>, document.getElementById('root'));

mount阶段

首次执行ReactDOM.render会创建fiberRootNode(源码中叫fiberRoot)和rootFiber。其中fiberRootNode是整个应用的根节点,rootFiber是所在组件树的根节点。(之所以要区分fiberRootNode与rootFiber,是因为在应用中我们可以多次调用ReactDOM.render渲染不同的组件树,他们会拥有不同的rootFiber。但是整个应用的根节点只有一个,那就是fiberRootNode。)

ReactDOM.render(<A/>, dom) // rootFiberA
ReactDOM.render(<B/>, dom) // rootFiberB
ReactDOM.render(<C/>, dom) // rootFiberC

fiberRootNode的current会指向当前页面上已渲染内容对应Fiber树,即current Fiber树。
在这里插入图片描述

fiberRootNode.current = rootFiber;

由于是首屏渲染,页面中还没有挂载任何DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(即current Fiber树为空)。

render阶段

接下来进入render阶段,根据组件返回的JSX在内存中依次创建Fiber节点并连接在一起构建Fiber树,被称为workInProgress Fiber树。(下图中右侧为内存中构建的树,左侧为页面显示的树)。在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性,在首屏渲染时只有rootFiber存在对应的current fiber(即rootFiber.alternate)。
在这里插入图片描述
commit阶段

render阶段结束之后,以及有两颗树了,一个Current Fiber Tree在视图显示,一直WorkInProgress Fiber Tree在内存中构建然后,然后在commit阶段修改fiberRootNode的current指针指向workInProgress Fiber树使其变为current Fiber 树,试图更新。
在这里插入图片描述
update阶段

当节点发生更新,就会进入update阶段,再一次进入render阶段,和上面一样根据JSX生成FIber节点,进而构建FIber树(可复用Current FIber Tree的节点)
在这里插入图片描述
workInProgress Fiber 树在render阶段完成构建后进入commit阶段渲染到页面上。渲染完毕后,workInProgress Fiber 树变为current Fiber 树。
在这里插入图片描述

Reconciler协调器

Reconciler是独立React之外的包,支持其他包引用,包名:react-reconciler,主要功能如下:

  • 输入: 暴露api函数(如: scheduleUpdateOnFiber), 供给其他包(如react包)调用.
  • 注册调度任务: 与调度中心(scheduler包)交互, 注册调度任务task, 等待任务回调.
  • 执行任务回调: 在内存中构造出fiber树, 同时与与渲染器(react-dom)交互, 在- 内存中创建出与fiber对应的DOM节点.
  • 输出: 与Renderer渲染器(react-dom)交互, 渲染DOM节点.

以上功能源码都集中在ReactFiberWorkLoop.js中. 现在将这些功能(从输入到输出)串联起来, 用下图表示:
在这里插入图片描述

  • workInProgress: 代表当前已创建的workInProgress fiber。
  • performUnitOfWork: 创建下一个Fiber节点并赋值给workInProgress,并将workInProgress与已创建的Fiber节点连接起来构成Fiber树。

我们知道Fiber Reconciler是从Stack Reconciler重构而来,通过遍历的方式实现可中断的递归,所以performUnitOfWork的工作可以分为两部分:“递”和“归”。

“递”阶段

首先从rootFiber开始向下深度优先遍历(DFS)。为遍历到的每个Fiber节点调用beginWork方法 。该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。当遍历到叶子节点(即没有子组件的组件)时就会进入下面的“归”阶段。

“归”阶段

在“归”阶段会调用completeWork 处理Fiber节点。当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(即fiber.sibling !== null),会进入其兄弟Fiber的“递”阶段。如果不存在兄弟Fiber,会进入父级Fiber的“归”阶段。“递”和“归”阶段会交错执行直到“归”到rootFiber。至此,render阶段的工作就结束了。然后进入commit阶段(Renderer渲染器)

以为下面代码为例

function  App() {return (<div>i am<span>KaSong</span></div>)
}ReactDOM.render(<App />, document.getElementById("root"));

其对应的FIber树为:
在这里插入图片描述
render阶段会依次执行:

1. rootFiber beginWork
2. App Fiber beginWork
3. div Fiber beginWork
4. "i am" Fiber beginWork
5. "i am" Fiber completeWork
6. span Fiber beginWork
7. span Fiber completeWork
8. div Fiber completeWork
9. App Fiber completeWork
10. rootFiber completeWork

之所以没有 “KaSong” Fiber 的 beginWork/completeWork,是因为作为一种性能优化手段,针对只有单一文本子节点的Fiber,React会特殊处理。

这里可以看出,整个递归过程中,主要就是调用beginWork、completeWork函数,下面主要分析这两个函数的作用。

BeginWork

beginWork源码
beginWork函数主要是传入一个current fiber节点来生成子fiber节点的过程,从代码来看,接受三个参数:

  • current:当前组件对应的Fiber节点在上一次更新时的Fiber节点,即workInProgress.alternate
  • workInProgress:当前组件对应的Fiber节点
  • renderLanes:优先级相关,Scheduler调度器中设置
function beginWork(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
): Fiber | null {// ...省略函数体
}

从双缓存机制一节我们知道,除rootFiber以外, 组件mount时,由于是首次渲染,是不存在当前组件对应的Fiber节点在上一次更新时的Fiber节点,即mount时current === null。所以我们可以根据current来判断当前是mount还是update。所以可以从这两个方面来分析:

  • update时:如果current存在,在满足一定条件时可以复用current节点,这样就能克隆current.child作为workInProgress.child,而不需要新建workInProgress.child。
  • mount时:除fiberRootNode以外,current === null。会根据fiber.tag不同,创建不同类型的子Fiber节点
function  beginWork(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes
): Fiber | null {// update时:如果current存在可能存在优化路径,可以复用current(即上一次更新的Fiber节点)if (current !== null) {// ...省略// 复用currentreturn bailoutOnAlreadyFinishedWork(current,workInProgress,renderLanes,);} else {didReceiveUpdate = false;}// mount时:根据tag不同,创建不同的子Fiber节点switch (workInProgress.tag) {case IndeterminateComponent: // ...省略case LazyComponent: // ...省略case FunctionComponent: // ...省略case ClassComponent: // ...省略case HostRoot:// ...省略case HostComponent:// ...省略case HostText:// ...省略// ...省略其他类型}
}

update时
我们可以看到,满足如下情况时didReceiveUpdate === false(即可以直接复用前一次更新的子Fiber,不需要新建子Fiber)

  • oldProps === newProps && workInProgress.type === current.type,即props与fiber.type不变
  • !includesSomeLane(renderLanes, updateLanes),即当前Fiber节点优先级不够
if (current !== null) {const oldProps = current.memoizedProps;   const newProps = workInProgress.pendingProps;if (oldProps !== newProps ||hasLegacyContextChanged() ||(__DEV__ ? workInProgress.type !== current.type : false)) {didReceiveUpdate = true;} else if (!includesSomeLane(renderLanes, updateLanes)) {didReceiveUpdate = false;switch (workInProgress.tag) {// 省略处理}return bailoutOnAlreadyFinishedWork(current,workInProgress,renderLanes,);} else {didReceiveUpdate = false;}} else {didReceiveUpdate = false;}

mount时
当不满足条件时就需要根据tag来新建子Fiber。Tag枚举

// mount时:根据tag不同,创建不同的Fiber节点
switch (workInProgress.tag) {case IndeterminateComponent: // ...省略case LazyComponent: // ...省略case FunctionComponent: // ...省略case ClassComponent: // ...省略case HostRoot:// ...省略case HostComponent:// ...省略case HostText:// ...省略// ...省略其他类型
}

对于我们常见的组件类型,如(FunctionComponent/ClassComponent/HostComponent),最终会进入reconcileChildren 方法

reconcileChildren
reconcileChildren函数主要做了一下事情:

  • 对于mount的组件,他会创建新的子Fiber节点
  • 对于update的组件,他会将当前组件与该组件在上次更新时对应的Fiber节点比较(也就是俗称的Diff算法),将比较的结果生成新Fiber节点
export function reconcileChildren(current: Fiber | null,workInProgress: Fiber,nextChildren: any,renderLanes: Lanes
) {if (current === null) {// 对于mount的组件workInProgress.child = mountChildFibers(workInProgress,null,nextChildren,renderLanes,);} else {// 对于update的组件workInProgress.child = reconcileChildFibers(workInProgress,current.child,nextChildren,renderLanes,);}
}

可以看出,也是通过current来区分是mount还是update,但是不管那个阶段最后都会生成一个子fiber节点并赋值给workInProgress.child

值得一提的是,mountChildFibers与reconcileChildFibers这两个方法的逻辑基本一致。唯一的区别是:reconcileChildFibers会为生成的Fiber节点带上effectTag属性,而mountChildFibers不会。mount阶段只有rootfiber节点有effectTag属性,调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下

effectTag
我们知道Reconciler主要就是对比构建Fiber树,并标记节点需要进行的操作,就是通过effectTag属性以二进制来保存需要做什么操作, 后面使用flags来替换了effect

function getFiberFlags(fiber: Fiber): number {// The name of this field changed from "effectTag" to "flags"return fiber.flags !== undefined ? fiber.flags : (fiber: any).effectTag;
}
// DOM需要插入到页面中
export const Placement = /*                */ 0b00000000000010;     
// DOM需要更新
export const Update = /*                   */ 0b00000000000100;
// DOM需要插入到页面中并更新
export const PlacementAndUpdate = /*       */ 0b00000000000110;
// DOM需要删除
export const Deletion = /*                 */ 0b00000000001000;

beginWork流程图
在这里插入图片描述

CompleteWork

类似beginWork,completeWork也是针对不同fiber.tag调用不同的处理逻辑

function completeWork(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
): Fiber | null {const newProps = workInProgress.pendingProps;switch (workInProgress.tag) {case IndeterminateComponent:case LazyComponent:case SimpleMemoComponent:case FunctionComponent:case ForwardRef:case Fragment:case Mode:case Profiler:case ContextConsumer:case MemoComponent:return null;case ClassComponent: {// ...省略return null;}case HostRoot: {// ...省略updateHostContainer(workInProgress);return null;}case HostComponent: {// ...省略return null;}// ...省略

其中需要注意注页面渲染所必须的HostComponent(即原生DOM组件对应的Fiber节点)。它和beginWork一样,我们根据current === null ?判断是mount还是update。同时针对HostComponent,判断update时我们还需要考虑workInProgress.stateNode != null ?(即该Fiber节点是否存在对应的DOM节点)

case HostComponent: {popHostContext(workInProgress);const rootContainerInstance = getRootHostContainer(); const type = workInProgress.type;if (current !== null && workInProgress.stateNode != null) {// update的情况// ...省略} else {// mount的情况// ...省略}return null;
}

mount时
mount时的主要逻辑包括三个:

  • 为Fiber节点生成对应的DOM节点
  • 将子孙DOM节点插入刚生成的DOM节点中
  • 与update逻辑中的updateHostComponent类似的处理props的过程
const currentHostContext = getHostContext();
// 为fiber创建对应DOM节点
const instance = createInstance(type,newProps,rootContainerInstance,currentHostContext,workInProgress,);
// 将子孙DOM节点插入刚生成的DOM节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM节点赋值给fiber.stateNode
workInProgress.stateNode = instance;// 与update逻辑中的updateHostComponent类似的处理props的过程
if (finalizeInitialChildren(instance,type,newProps,rootContainerInstance,currentHostContext,)
) {markUpdate(workInProgress);
}

update时
当节点进行update的时候,fiber节点已经存在对应的Dom节点了,所以主要处理的是props和一些回调

  • onClick、onChange等回调函数的注册
  • 处理style prop
  • 处理DANGEROUSLY_SET_INNER_HTML prop
  • 处理children prop
updateHostComponent = function(                current: Fiber,workInProgress: Fiber,type: Type,newProps: Props,rootContainerInstance: Container,) {const oldProps = current.memoizedProps;if (oldProps === newProps) {return;}const instance: Instance = workInProgress.stateNode;const currentHostContext = getHostContext();const updatePayload = prepareUpdate(instance,type,oldProps,newProps,rootContainerInstance,currentHostContext,);workInProgress.updateQueue = (updatePayload: any);if (updatePayload) {markUpdate(workInProgress);}};

被处理完的props会保存在workInProgress.updateQueue(以key,value形式的数组,奇数下标为key,偶数下标为value),在commit阶段进行更新。

effectList
在completeWork的上层函数completeUnitOfWork中,每个执行完completeWork且存在effectTag的Fiber节点会被保存在一条被称为effectList的单向链表中,最后通过“归”阶段到rootfiber节点,形成以rootfiber为起点的单向链表。
在这里插入图片描述
completeWork流程图
在这里插入图片描述

至此,render阶段全部工作完成。在performSyncWorkOnRoot函数中fiberRootNode被传递给commitRoot方法,开启commit阶段工作流程。然后在commit阶段,会遍历整个effectList来更新对应的dom节点(fiber.stateNode保存的fiber对应的dom节点)

commitRoot(root);

参考文档

React技术揭秘
图解React

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

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

相关文章

C++_虚函数表

虚函数表 介绍源码运行结果笔记扩充函数名联编静态联编动态联编 介绍 1.编译器通过指针或引用调用虚函数&#xff0c;不会立即生成函数调用指令&#xff0c;而是用 二级函数指针 代替 1.1确定真实类型 1.2找到虚函数表从而找到入口地址 1.3根据入口地址调用函数(PS:俗称 函数指…

RT-Thread入门笔记3-线程的创建

线程 RT-Thread 中&#xff0c;线程由三部分组成&#xff1a;线程代码&#xff08;入口函数&#xff09;、线程控制块、线程堆栈. 线程代码: 线程控制块 : 线程控制块是操作系统用于管理线程的一个数据结构&#xff0c; 它会存放线程的一些信息&#xff0c; 例如优先级、 线程…

使用Notepad++将多行数据合并成一行

步骤 1、按CtrlF&#xff0c;弹出“替换”的窗口&#xff1b; 2、选择“替换”菜单&#xff1b; 3、“查找目标”内容输入为&#xff1a;\r\n&#xff1b; 4、“替换为”内容为空&#xff1b; 5、“查找模式”选择为正则表达式&#xff1b; 6、设置好之后&#xff0c;点击“全…

Vue3 父事件覆盖子事件,Vue2 的 v-on=“$listeners“ 的替代方案

在 Vue3 中&#xff0c;$listeners 被删除 子组件代码&#xff0c;需要特别注意的是事件名为 on 开头&#xff0c;例如 onBack。不确定的可以通过给父组件传递 事件或属性&#xff0c;再打印子组件的 attrs useAttrs()&#xff0c;来确定传值 // template v-bind"newA…

了解PyTorch中的缩放点积注意力及演示

torch.nn.functional.scaled_dot_product_attention 函数在 PyTorch 框架中用于实现缩放点积注意力&#xff08;Scaled Dot-Product Attention&#xff09;。这是一种在自然语言处理和计算机视觉等领域常用的注意力机制。它的主要目的是通过计算查询&#xff08;query&#xff…

Linux ----冯诺依曼体系结构与操作系统

目录 前言 一、冯诺依曼体系结构 二、为什么选择冯诺依曼体系结构&#xff1f; 三、使用冯诺依曼结构解释问题 问题1&#xff1a; 问题2: 四、操作系统 1.操作系统是什么 2.为什么需要操作系统 3.操作系统怎样管理的 4.如何给用户提供良好环境 五、我们是怎样调用系…

Github 2024-01-12Java开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-01-12统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Java项目10Vue项目3 Apache Flink: 开源流处理框架 创建周期&#xff1a;3506 天开发语言&#xff1a;Java协…

iOS rootless无根越狱解决方案

据游戏工委数据统计&#xff0c;2023年国内游戏市场实际销售收入与用户规模双双创下新高&#xff0c;游戏普遍采用多端并发方式&#xff0c;成为收入增长的主因之一。 中国市场实际销售收入及增长率丨数据来源&#xff1a;游戏工委 多端互通既是机遇&#xff0c;也是挑战。从游…

【AI大模型应用开发】1.0 Prompt Engineering(提示词工程)- 典型构成、原则与技巧,代码中加入Prompt

从这篇文章开始&#xff0c;我们就正式开始学习AI大模型应用开发的相关知识了。首先是提示词工程&#xff08;Prompt Engineering&#xff09;。 文章目录 0. 什么是提示词&#xff08;Prompt&#xff09;1. 为什么Prompt会起作用 - 大模型工作原理2. Prompt的典型构成、原则与…

【5】商密测评密码辅助工具

0X01 前言 最近在学了下商密测评&#xff0c;研究了下技术层面的测评&#xff0c;感觉找工具不方便&#xff0c;就顺手自己造了个辅助工具&#xff0c;都是自己遇到需要用的。 0x02 工具功能介绍 不爱打字&#xff0c;直接上图。后续根据技术测评层面需要继续完善和增加功能。…

基于VSG控制的MMC并网逆变器MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 模型简介 根据传统同步发电机的运行特性设计了MMC-VSG功频控制器和励磁控制器&#xff0c; 实现了MMC-VSG逆变器对高压电网电压和频率的支撑。该模型包含MMC变流器模块&#xff0c;环流抑制模块&#xff0c;…

HackTheBox-Keeper

OpenVPN连接 连接上HackTheBox&#xff01; 同时找到这个靶机&#xff0c;进行join&#xff01;分配的靶机的地址位10.10.11.227&#xff01; 信息收集 nmap -sT --min-rate 10000 -p- 10.10.11.227 开放端口为22和80端口 服务版本和操作系统信息探测&#xff1a; nmap -s…

Milvus Cloud与携程的向量探索大公开

【User Tech】2024 我们来啦&#xff01; 今年&#xff0c;【User Tech】将更加专注于为社区用户提供技术功能解读、热点答疑&#xff0c;聚焦更丰富、更多样化的行业或使用场景的用户案例。我们期待通过分享更多关于 Milvus Cloud 的实战经验&#xff0c;为大家在 AI、大模型、…

YOLOv8算法改进【NO.98】改进损失函数为最新提出的Shape-IoU

前 言 YOLO算法改进系列出到这&#xff0c;很多朋友问改进如何选择是最佳的&#xff0c;下面我就根据个人多年的写作发文章以及指导发文章的经验来看&#xff0c;按照优先顺序进行排序讲解YOLO算法改进方法的顺序选择。具体有需求的同学可以私信我沟通&#xff1a; 第一…

李沐之经典卷积神经网络

目录 1. LeNet 2. 代码实现 1. LeNet 输入是32*32图片&#xff0c;放到一个5*5的卷积层里面&#xff0c;卷积层的输出通道数是6&#xff0c;高宽都是28&#xff08;32-5128&#xff09;。再经过2*2的池化层&#xff0c;把28*28变成14*14&#xff08;28-22&#xff09;/214&am…

《Vue2 进阶知识》动态挂载组件之Vue.extend + vm.$mount

前言 目前工作还是以 Vue2 为主&#xff0c;今早有人提问 如何动态挂载组件&#xff1f; 话说很久很久以前就实现过&#xff0c;今天再详细的整理一下此问题&#xff01; 开始 动态组件如下&#xff0c;是个简单的例子&#xff1a; 但请注意这里给了个 id"test2"…

vue 组件 import make sure to provide the “name“ option.

百度了好多结果&#xff0c;都过时了&#xff0c;例如&#xff1a; 模块引入是否加{} 再比如&#xff1a; 对于递归组件&#xff0c;请确保提供“name”选项。 出现该错误情况之一&#xff1a; 错误由未正确引入组件或子组件引起&#xff0c;如element-ui中form表单组件未引…

PostgreSQL之SEMI-JOIN半连接

什么是Semi-Join半连接 Semi-Join半连接&#xff0c;当外表在内表中找到匹配的记录之后&#xff0c;Semi-Join会返回外表中的记录。但即使在内表中找到多条匹配的记录&#xff0c;外表也只会返回已经存在于外表中的记录。而对于子查询&#xff0c;外表的每个符合条件的元组都要…

GitLab 502 Whoops, GitLab is taking too much time to respond. 解决

1、先通过gitlab-ctl restart进行重启&#xff0c;2分钟后看是否可以正常访问&#xff0c;为什么要2分钟&#xff0c;因为gitlab启动会有很多配套的服务启动&#xff0c;包括postgresql等 2、如果上面不行&#xff0c;再看gitlab日志&#xff0c;通过gitlab-ctl tail命令查看&…

【Arduino】编程语言:定时函数、数学函数、字符函数(功能、语法格式、参数说明、返回值) | 软件开发环境:安装步骤介绍(EXE安装版、ZIP安装版)

你的负担将变成礼物,你受的苦将照亮你的路。———泰戈尔 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌿[2] 2023年城市之星领跑者TOP1(哈尔滨)🌿 🌟[3] 2022年度博客之星人工智能领域TOP4🌟 🏅[4] 阿里云社区…