react源码分析:组件的创建和更新
这一章节就来讲讲ReactDOM.render()方法的内部实现与流程吧。
因为初始化的源码文件部分所涵盖的内容很多,包括创建渲染、更新渲染、Fiber树的创建与diff,element的创建与插入,还包括一些优化算法,所以我就整个的React执行流程画了一个简单的示意图。
React源码执行流程图

从图中我们很清晰的看到ReactDOM.render()之后我们的组件具体干了什么事情,那么我们进入源码文件一探究竟吧。
// packages/react-dom/src/client/ReactDOMLegacy.js
export function render(element: React$Element<any>, // 经过babel解析后的elementcontainer: Container, // 根组件节点: document.getElementById('root')..callback: ?Function,// 回调
) {// 做合法容器的验证(根组件)invariant(isValidContainer(container),'Target container is not a DOM element.',);// 开发模式下if (__DEV__) {const isModernRoot =isContainerMarkedAsRoot(container) &&container._reactRootContainer === undefined;if (isModernRoot) {console.error('You are calling ReactDOM.render() on a container that was previously ' +'passed to ReactDOM.createRoot(). This is not supported. ' +'Did you mean to call root.render(element)?',);}}// 返回 legacyRenderSubtreeIntoContainerreturn legacyRenderSubtreeIntoContainer(null,element,container,false,callback,);
}
所以当前render函数仅仅只是做了部分逻辑,阅读React源码,给你一个直观的感受就是他拆分的颗粒度非常的细,很多重复命名的函数,可能是见名知意的变量名只有那么几个常见的组合吧,这也是React作者的用心良苦吧。
追根究底我们还是得看一看legacyRenderSubtreeIntoContainer究竟干了些不为人知的事情呢
legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer(parentComponent: ?React$Component<any, any>, // 父级组件children: ReactNodeList, // 当前元素container: Container, // 容器 eg:getElementById('root')forceHydrate: boolean, callback: ?Function,
) {if (__DEV__) {topLevelUpdateWarnings(container);warnOnInvalidCallback(callback === undefined ? null : callback, 'render');}// TODO: Without `any` type, Flow says "Property cannot be accessed on any// member of intersection type." Whyyyyyy.let root: RootType = (container._reactRootContainer: any);let fiberRoot;// 如果有根组件,表示不是初始化渲染,则走下面的批量更新// 没有根组件,那么就要去创建根组件了if (!root) {// 初始化挂载root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container,forceHydrate,);fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// 不必要的批量更新unbatchedUpdates(() => {updateContainer(children, fiberRoot, parentComponent, callback);});} else {fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// 批量更新updateContainer(children, fiberRoot, parentComponent, callback);}return getPublicRootInstance(fiberRoot);
}
- 有根节点的情况下,我们判定为非首次渲染状态,执行
updateContainer - 没有根节点的情况下,我们判定为首次渲染,接着去创建根节点,执行
legacyCreateRootFromDOMContainer,拿到了root之后,我们会去触发执行updateContainer
legacyCreateRootFromDOMContainer
function legacyCreateRootFromDOMContainer(container: Container, // 容器forceHydrate: boolean, // value:false
): RootType {const shouldHydrate =forceHydrate || shouldHydrateDueToLegacyHeuristic(container);// First clear any existing content.if (!shouldHydrate) {let warned = false;let rootSibling;while ((rootSibling = container.lastChild)) {if (__DEV__) {if (!warned &&rootSibling.nodeType === ELEMENT_NODE &&(rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)) {warned = true;console.error('render(): Target node has markup rendered by React, but there ' +'are unrelated nodes as well. This is most commonly caused by ' +'white-space inserted around server-rendered markup.',);}}container.removeChild(rootSibling);}}if (__DEV__) {if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {warnedAboutHydrateAPI = true;console.warn('render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +'will stop working in React v18. Replace the ReactDOM.render() call ' +'with ReactDOM.hydrate() if you want React to attach to the server HTML.',);}}// 关注createLegacyRootreturn createLegacyRoot(container,shouldHydrate? {hydrate: true,}: undefined,);
}
createLegacyRoot
export function createLegacyRoot(container: Container, // 容器options?: RootOptions,
): RootType {//关注ReactDOMBlockingRootreturn new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
ReactDOMBlockingRoot
function ReactDOMBlockingRoot(container: Container, // 容器tag: RootTag, // LegacyRoot = 0;BlockingRoot = 1;ConcurrentRoot = 2;options: void | RootOptions,
) {this._internalRoot = createRootImpl(container, tag, options);
}
- 我们在这里看到
this._internalRoot出来了,因为在先前这个值会给到fiberRoot,所以我们再去看一看这个_internalRoot是怎么创建出来的
createRootImpl
function createRootImpl(container: Container, tag: RootTag, options: void | RootOptions,
) {// Tag is either LegacyRoot or Concurrent Rootconst hydrate = options != null && options.hydrate === true;const hydrationCallbacks =(options != null && options.hydrationOptions) || null;const mutableSources =(options != null &&options.hydrationOptions != null &&options.hydrationOptions.mutableSources) ||null;// 关注createContainerconst root = createContainer(container, tag, hydrate, hydrationCallbacks);markContainerAsRoot(root.current, container);const containerNodeType = container.nodeType;if (enableEagerRootListeners) {const rootContainerElement =container.nodeType === COMMENT_NODE ? container.parentNode : container;listenToAllSupportedEvents(rootContainerElement);} else {if (hydrate && tag !== LegacyRoot) {const doc =containerNodeType === DOCUMENT_NODE? container: container.ownerDocument;// We need to cast this because Flow doesn't work// with the hoisted containerNodeType. If we inline// it, then Flow doesn't complain. We intentionally// hoist it to reduce code-size.eagerlyTrapReplayableEvents(container, ((doc: any): Document));} else if (containerNodeType !== DOCUMENT_FRAGMENT_NODE &&containerNodeType !== DOCUMENT_NODE) {ensureListeningTo(container, 'onMouseEnter', null);}}if (mutableSources) {for (let i = 0; i < mutableSources.length; i++) {const mutableSource = mutableSources[i];registerMutableSourceForHydration(root, mutableSource);}}// 关注rootreturn root;
}
相关参考视频讲解:进入学习
- 见名知意
关注createContainer为创建容器,看其源码
createContainer
// packages/react-reconciler/src/ReactFiberReconciler.old.js
export function createContainer(containerInfo: Container, // 容器tag: RootTag, // LegacyRoot = 0;BlockingRoot = 1;ConcurrentRoot = 2;hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {// 关注createFiberRootreturn createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
createFiberRoot
export function createFiberRoot(containerInfo: any, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);if (enableSuspenseCallback) {root.hydrationCallbacks = hydrationCallbacks;}// 关注createHostRootFiberconst uninitializedFiber = createHostRootFiber(tag);root.current = uninitializedFiber;uninitializedFiber.stateNode = root;// 初始化更新队列initializeUpdateQueue(uninitializedFiber);return root;
}
- 关注
root.current、uninitializedFiber.stateNode这两个玩意儿,后面有大作用,我们还是看看createHostRootFiber吧
createHostRootFiber
export function createHostRootFiber(tag: RootTag): Fiber {let mode;if (tag === ConcurrentRoot) {mode = ConcurrentMode | BlockingMode | StrictMode;} else if (tag === BlockingRoot) {mode = BlockingMode | StrictMode;} else {mode = NoMode;}if (enableProfilerTimer && isDevToolsPresent) {// Always collect profile timings when DevTools are present.// This enables DevTools to start capturing timing at any point–// Without some nodes in the tree having empty base times.mode |= ProfileMode;}return createFiber(HostRoot, null, null, mode);
}
- 一眼望去这里便是对
tag的处理,到了后面便是去创建fiber节点
createFiber
const createFiber = function(tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,
): Fiber {// $FlowFixMe: the shapes are exact here but Flow doesn't like constructorsreturn new FiberNode(tag, pendingProps, key, mode);
};
- 那么主角出来了,就是我们的
FiberNode,这里才走完初始化的创建流程,
所以大致的流程就是上面的图里画的那样子,创建流程我们就告一段落,那我们再去看看更新的流程是怎么玩的。
我们知道除了ReactDOM.render()会触发更新流程之外,我们还有setState、强制更新、hooks里面的setXxxx等等手段可以触发更新,所谓setState那么不正好是我们Component原型上挂的方法嘛。我们回顾一下Component,那些更新都是调用了updater触发器上的方法,那么我们去看一下这个东西。
const classComponentUpdater = {isMounted,// setStateenqueueSetState(inst, payload, callback) {const fiber = getInstance(inst);const eventTime = requestEventTime(); // 获取更新触发的时间const lane = requestUpdateLane(fiber); // 获取任务优先级//根据更新触发时间 + 更新优先级来创建更新任务对象const update = createUpdate(eventTime, lane); // 创建更新任务对象// const update: Update<*> = {// eventTime, // 更新时间// lane, // 优先级// tag: UpdateState, // 更新类型:0更新,1替换。,2强制替换,3捕获型更新// payload: null,// 需要更新的内容// callback: null, // 更新完后的回调// next: null, // 指向下一个更新// };// 把内容填上update.payload = payload;if (callback !== undefined && callback !== null) {if (__DEV__) {// 开发环境下腰给个警告warnOnInvalidCallback(callback, 'setState');}// 如果有回调,那么加上回调update.callback = callback;}// const update: Update<*> = {// eventTime, // 更新时间 you// lane, // 优先级 you // tag: UpdateState, // 更新类型:0更新,1替换。,2强制替换,3捕获型更新// payload: null,// 需要更新的内容 you// callback: null, // 更新完后的回调 you// next: null, // 指向下一个更新// };enqueueUpdate(fiber, update);// 推入更新队列scheduleUpdateOnFiber(fiber, lane, eventTime);// 调度if (__DEV__) {if (enableDebugTracing) {if (fiber.mode & DebugTracingMode) {const name = getComponentName(fiber.type) || 'Unknown';logStateUpdateScheduled(name, lane, payload);}}}if (enableSchedulingProfiler) {markStateUpdateScheduled(fiber, lane);}},// replaceStateenqueueReplaceState(inst, payload, callback) {const fiber = getInstance(inst);const eventTime = requestEventTime();const lane = requestUpdateLane(fiber);const update = createUpdate(eventTime, lane);update.tag = ReplaceState;update.payload = payload;if (callback !== undefined && callback !== null) {if (__DEV__) {warnOnInvalidCallback(callback, 'replaceState');}update.callback = callback;}enqueueUpdate(fiber, update);scheduleUpdateOnFiber(fiber, lane, eventTime);if (__DEV__) {if (enableDebugTracing) {if (fiber.mode & DebugTracingMode) {const name = getComponentName(fiber.type) || 'Unknown';logStateUpdateScheduled(name, lane, payload);}}}if (enableSchedulingProfiler) {markStateUpdateScheduled(fiber, lane);}},// forceUpdateenqueueForceUpdate(inst, callback) {const fiber = getInstance(inst);const eventTime = requestEventTime();const lane = requestUpdateLane(fiber);const update = createUpdate(eventTime, lane);update.tag = ForceUpdate;if (callback !== undefined && callback !== null) {if (__DEV__) {warnOnInvalidCallback(callback, 'forceUpdate');}update.callback = callback;}enqueueUpdate(fiber, update);scheduleUpdateOnFiber(fiber, lane, eventTime);if (__DEV__) {if (enableDebugTracing) {if (fiber.mode & DebugTracingMode) {const name = getComponentName(fiber.type) || 'Unknown';logForceUpdateScheduled(name, lane);}}}if (enableSchedulingProfiler) {markForceUpdateScheduled(fiber, lane);}},
};
updateContainer
export function updateContainer(element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, callback: ?Function,
): Lane {if (__DEV__) {onScheduleRoot(container, element);}const current = container.current;const eventTime = requestEventTime();if (__DEV__) {// $FlowExpectedError - jest isn't a global, and isn't recognized outside of testsif ('undefined' !== typeof jest) {warnIfUnmockedScheduler(current);warnIfNotScopedWithMatchingAct(current);}}const lane = requestUpdateLane(current);if (enableSchedulingProfiler) {markRenderScheduled(lane);}const context = getContextForSubtree(parentComponent);if (container.context === null) {container.context = context;} else {container.pendingContext = context;}if (__DEV__) {if (ReactCurrentFiberIsRendering &&ReactCurrentFiberCurrent !== null &&!didWarnAboutNestedUpdates) {didWarnAboutNestedUpdates = true;console.error('Render methods should be a pure function of props and state; ' +'triggering nested component updates from render is not allowed. ' +'If necessary, trigger nested updates in componentDidUpdate.\n\n' +'Check the render method of %s.',getComponentName(ReactCurrentFiberCurrent.type) || 'Unknown',);}}const update = createUpdate(eventTime, lane);// 创建更新任务// Caution: React DevTools currently depends on this property// being called "element".update.payload = {element};callback = callback === undefined ? null : callback;if (callback !== null) {if (__DEV__) {if (typeof callback !== 'function') {console.error('render(...): Expected the last optional `callback` argument to be a ' +'function. Instead received: %s.',callback,);}}update.callback = callback;}enqueueUpdate(current, update); // 推入更新队列scheduleUpdateOnFiber(current, lane, eventTime); // 进行调度return lane;
}
- 我们看到了
enqueueSetState、enqueueReplaceState、enqueueForceUpdate还是初始化时候走的updateContainer都是走了几乎一样的逻辑:requestEventTime=>requestUpdateLane=>createUpdate=>enqueueUpdate=>scheduleUpdateOnFiber
总结
本章从ReactDOM.render()开始讲解了,初始化的时候,根节点的创建与更新流程,以及在类组件原型上挂载的一些更新的方法,但是为什么这一章不直接把他更新流程讲完呢?因为下一章要讲一下fiberNode这个东西,简而言之他只是一个架构概念,并不是React独有的,但是现在很有必要一起来看一看这个,那么下一章我们来一起揭开FiberNode的神秘面纱吧
相关文章:
react源码分析:组件的创建和更新
这一章节就来讲讲ReactDOM.render()方法的内部实现与流程吧。 因为初始化的源码文件部分所涵盖的内容很多,包括创建渲染、更新渲染、Fiber树的创建与diff,element的创建与插入,还包括一些优化算法,所以我就整个的React执行流程画了…...
Android Lmkd 低内存终止守护程序
一、低内存终止守护程序 Android 低内存终止守护程序 (lmkd) 进程可监控运行中的 Android 系统的内存状态,并通过终止最不必要的进程来应对内存压力大的问题,使系统以可接受的性能水平运行。 所有应用进程都是从zygote孵化出来的,记录在AMS…...
快速掌握 Flutter 图片开发核心技能
大家好,我是 17。 在 Flutter 中使用图片是最基础能力之一。17 做了精心准备,满满的都是干货!本文介绍如何在 Flutter 中使用图片,尽量详细,示例完整,包会! 使用网络图片 使用网络图片超级简…...
复习使用git(二)
删除远程分支 git push origin --delete 分支名 撤销修改 撤销工作区的修改 已修改,但尚未添加(add),使用 git restore 文件名 撤销工作区的修改。 Note: “git checkout – 文件名”,checkout 检出的意思&#x…...
魔兽世界335服务端架设对外网开放的步骤
警告:在没有网络安全防护措施或基础知识的情况下,开放端口可能造成被黑客入侵、流量攻击、破坏数据、资料泄露等情况的发生。在你选择开放端口时,视为已经充分了解可能发生的后果、危害,清楚自己在做什么,并且自己将对…...
华为OD机试模拟题 用 C++ 实现 - 通信误码(2023.Q1)
最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明通信误码题目输入输出示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,...
Vue 核心
文章目录Vue 核心一,Vue 简介(一)官网(二)介绍与描述(三)Vue 的特点(四)与其它 JS 框架的关联(五)Vue 周边库二,初识 Vue三࿰…...
Kylin V10桌面版arm3568 源码安装redis
上传redis-5.0.14.tar.gz到/home/kylin/下载;解压kylinkylin:~/下载$ tar -zxvf redis-5.0.14.tar.gz/opt下新建redis目录,并将上面解压的文件夹移到此处kylinkylin:~/下载$ sudo mv redis-5.0.14 /opt/redis/编译:kylinkylin:/opt/redis/red…...
【ICCV2022】 CAPAO:一种高效的单阶段人体姿态估计模型
CAPAO:一种高效的单阶段人体姿态估计模型 重新思考关键点表示:将关键点和姿态建模作为多人姿态估计的对象(Rethinking Keypoint Representations: Modeling Keypoints and Poses as Objects for Multi-Person Human Pose Estimation…...
ROS1学习笔记:ROS中的坐标管理系统(ubuntu20.04)
参考B站古月居ROS入门21讲:ROS中的坐标系管理系统 基于VMware Ubuntu 20.04 Noetic版本的环境 文章目录一、机器人中的坐标变换二、TF功能包三、小海龟跟随实验3.1 启动实验3.2 查看当前的TF树3.3 坐标相对位置可视化3.3.1 tf_echo3.3.2 rviz一、机器人中的坐标变换…...
requests---(2)session简介与自动写博客
目录:导读 session简介 session登录 自动写博客 获取登录cookies 抓取写博客接口 requests自动写博客 写在最后 http协议是无状态的,也就是每个请求都是独立的。那么登录后的一系列动作,都需要用cookie来验证身份是否是登录状态&#…...
基于 HAProxy + Keepalived 搭建 RabbitMQ 高可用集群
RabbitMQ 集群 通常情况下,在集群中我们把每一个服务称之为一个节点,在 RabbitMQ 集群中,节点类型可以分为两种: 内存节点:元数据存放于内存中。为了重启后能同步数据,内存节点会将磁盘节点的地址存放于磁…...
基于51单片机和proteus的智能调速风扇设计
此智能风扇是基于51单片机和proteus的仿真设计,功能如下: 1. Timer0 PWM控制电机转速 2. DHT11采集温湿度 3. LCD1602显示温湿度及电机状态 4. 按键控制电机加减速启停等 5. 串口控制电机加减速启停等 功能框图如下: Proteus仿真界面如下…...
SQL Server开启CDC的完整操作过程
这里写自定义目录标题写在前面SQL Server开启CDC1. 将指定库的实例先开启CDC2. 开启需要开启CDC的表3. 关闭CDC功能更详细信息参照官网写在前面 鉴于老旧数据的结构和项目都在sqlserver上存储,且迁移成本巨大,当下要为sqlserver的存储过程减负。要将一部…...
【Spring Cloud Alibaba】008-Sentinel
【Spring Cloud Alibaba】008-Sentinel 文章目录【Spring Cloud Alibaba】008-Sentinel一、服务雪崩1、概述2、解决方案常见的容错机制二、Sentinel:分布式系统的流量防卫兵1、**Sentinel** 概述简介特性Sentinel 的开源生态Sentinel 的历史2、Sentinel 基本概念资源…...
解读CRC校验计算
个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) 参考:http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html 参考:https://en.wikipedia.org/wiki/Cyclic_redundancy_check 参考:https://www.cnblogs.com/…...
深入理解Spring MVC下
上一篇博客从理论概念上来梳理Spring MVC相关知识,此篇博客将通过spring官网提供showcase代码为例子,详细介绍showcase代码中包含的各个例子是如何实现的。官网的showcase代码包含的主要例子包括,Demo地址:Mapping Requests&#…...
【Linux】ssh-keygen不需要回车,自动生成密钥,批量免密操作!
使用命令ssh-keygen 需要手动敲击回车,才会生成密钥,如下代码所示 [rootlocalhost ~]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): Enter passphrase (empty for no passphrase):…...
C/C++开发,无可避免的内存管理(篇四)-智能指针备选
一、智能指针 采用C/C开发堆内存管理无论是底层开发还是上层应用,无论是开发新手,还是多年的老手,都会不自觉中招,尤其是那些不是自己一手经历的代码,要追溯问题出在哪里更是个麻烦事。C/C程序常常会遇到程序突然退出&…...
VMware ESXi给虚拟机扩容
用ESXi管理的虚拟机硬盘空间不够了,讲一下如何进行扩容。 一、查看现状 通过如下三个命令,可以查看硬盘情况,可以看到只有500G,已经用了45%。这次我们再扩容500G。 df -Th lsblk fdisk -lIDE磁盘的文件名为 /de…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...
