scheduler工作阶段在React内部被称为schedule阶段。

在《React源码3》,我们已经将update加入队列并返回到了根容器节点root。

function updateContainer(element, container, parentComponent, callback) {//前面略过var root = enqueueUpdate(current$1, update, lane);if (root !== null) {scheduleUpdateOnFiber(root, current$1, lane, eventTime);entangleTransitions(root, current$1, lane);}return lane;
}

scheduleUpdateOnFiber函数是React中Schedule模块主要函数,用于调度更新,下面几种情况都会调用scheduleUpdateOnFiber函数:

  • 页面初次渲染
  • 类组件setState/forceUpdate
  • 函数组件setState

Schedule模块流程图

一、schdeuleUpdateOnFiber函数

function scheduleUpdateOnFiber(root, fiber, lane, eventTime) {checkForNestedUpdates();{if (isRunningInsertionEffect) {error('useInsertionEffect must not schedule updates.');}}{if (isFlushingPassiveEffects) {didScheduleUpdateDuringPassiveEffects = true;}} // Mark that the root has a pending update.markRootUpdated(root, lane, eventTime);if ((executionContext & RenderContext) !== NoLanes && root === workInProgressRoot) {// This update was dispatched during the render phase. This is a mistake// if the update originates from user space (with the exception of local// hook updates, which are handled differently and don't reach this// function), but there are some internal React features that use this as// an implementation detail, like selective hydration.warnAboutRenderPhaseUpdatesInDEV(fiber); // Track lanes that were updated during the render phase} else {// This is a normal update, scheduled from outside the render phase. For// example, during an input event.{if (isDevToolsPresent) {addFiberToLanesMap(root, fiber, lane);}}warnIfUpdatesNotWrappedWithActDEV(fiber);if (root === workInProgressRoot) {// Received an update to a tree that's in the middle of rendering. Mark// that there was an interleaved update work on this root. Unless the// `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render// phase update. In that case, we don't treat render phase updates as if// they were interleaved, for backwards compat reasons.if ( (executionContext & RenderContext) === NoContext) {workInProgressRootInterleavedUpdatedLanes = mergeLanes(workInProgressRootInterleavedUpdatedLanes, lane);}if (workInProgressRootExitStatus === RootSuspendedWithDelay) {// The root already suspended with a delay, which means this render// definitely won't finish. Since we have a new update, let's mark it as// suspended now, right before marking the incoming update. This has the// effect of interrupting the current render and switching to the update.// TODO: Make sure this doesn't override pings that happen while we've// already started rendering.markRootSuspended$1(root, workInProgressRootRenderLanes);}}ensureRootIsScheduled(root, eventTime);if (lane === SyncLane && executionContext === NoContext && (fiber.mode & ConcurrentMode) === NoMode && // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.!( ReactCurrentActQueue$1.isBatchingLegacy)) {// Flush the synchronous work now, unless we're already working or inside// a batch. This is intentionally inside scheduleUpdateOnFiber instead of// scheduleCallbackForFiber to preserve the ability to schedule a callback// without immediately flushing it. We only do this for user-initiated// updates, to preserve historical behavior of legacy mode.resetRenderTimer();flushSyncCallbacksOnlyInLegacyMode();}}
}

二、markRootUpdated函数      

负责标记根节点有新任务,并为调度系统提供最新的优先级和时间信息。

function markRootUpdated(root, updateLane, eventTime) {root.pendingLanes |= updateLane; // If there are any suspended transitions, it's possible this new update// could unblock them. Clear the suspended lanes so that we can try rendering// them again.//// TODO: We really only need to unsuspend only lanes that are in the// `subtreeLanes` of the updated fiber, or the update lanes of the return// path. This would exclude suspended updates in an unrelated sibling tree,// since there's no way for this update to unblock it.//// We don't do this if the incoming update is idle, because we never process// idle updates until after all the regular updates have finished; there's no// way it could unblock a transition.if (updateLane !== IdleLane) {root.suspendedLanes = NoLanes;root.pingedLanes = NoLanes;}var eventTimes = root.eventTimes;var index = laneToIndex(updateLane); // We can always overwrite an existing timestamp because we prefer the most// recent event, and we assume time is monotonically increasing.eventTimes[index] = eventTime;
}

三、ensureRootIsScheduled函数

确保当前 FiberRoot 上有一个合适优先级的调度任务被安排,即根据最新的更新优先级,决定是否需要新建、复用或取消调度任务,并最终调度同步或异步的渲染入口。

  1. 获取下一个需要处理的 lanes(优先级),如果没有任务,取消已存在的调度任务。
  2. 判断当前任务优先级是否和已存在的调度任务一致,如果一致则复用,不一致则取消旧任务。
  3. 根据优先级安排调度:
    • 如果是同步优先级(SyncLane),调度 performSyncWorkOnRoot。
    • 否则,调度performConcurrentWorkOnRoot ,并传递合适的 scheduler 优先级。
  4. 将调度任务的引用和优先级记录到 root 上。
function ensureRootIsScheduled(root, currentTime) {var existingCallbackNode = root.callbackNode; // Check if any lanes are being starved by other work. If so, mark them as// expired so we know to work on those next.markStarvedLanesAsExpired(root, currentTime); // Determine the next lanes to work on, and their priority.var nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);if (nextLanes === NoLanes) {// Special case: There's nothing to work on.if (existingCallbackNode !== null) {cancelCallback$1(existingCallbackNode);}root.callbackNode = null;root.callbackPriority = NoLane;return;} // We use the highest priority lane to represent the priority of the callback.var newCallbackPriority = getHighestPriorityLane(nextLanes); // Check if there's an existing task. We may be able to reuse it.var existingCallbackPriority = root.callbackPriority;if (existingCallbackPriority === newCallbackPriority && // Special case related to `act`. If the currently scheduled task is a// Scheduler task, rather than an `act` task, cancel it and re-scheduled// on the `act` queue.!( ReactCurrentActQueue$1.current !== null && existingCallbackNode !== fakeActCallbackNode)) {{// If we're going to re-use an existing task, it needs to exist.// Assume that discrete update microtasks are non-cancellable and null.// TODO: Temporary until we confirm this warning is not fired.if (existingCallbackNode == null && existingCallbackPriority !== SyncLane) {error('Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.');}} // The priority hasn't changed. We can reuse the existing task. Exit.return;}if (existingCallbackNode != null) {// Cancel the existing callback. We'll schedule a new one below.cancelCallback$1(existingCallbackNode);} // Schedule a new callback.var newCallbackNode;if (newCallbackPriority === SyncLane) {// Special case: Sync React callbacks are scheduled on a special// internal queueif (root.tag === LegacyRoot) {if ( ReactCurrentActQueue$1.isBatchingLegacy !== null) {ReactCurrentActQueue$1.didScheduleLegacyUpdate = true;}scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));} else {scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));}{// Flush the queue in a microtask.if ( ReactCurrentActQueue$1.current !== null) {// Inside `act`, use our internal `act` queue so that these get flushed// at the end of the current scope even when using the sync version// of `act`.ReactCurrentActQueue$1.current.push(flushSyncCallbacks);} else {scheduleMicrotask(function () {// In Safari, appending an iframe forces microtasks to run.// https://github.com/facebook/react/issues/22459// We don't support running callbacks in the middle of render// or commit so we need to check against that.if ((executionContext & (RenderContext | CommitContext)) === NoContext) {// Note that this would still prematurely flush the callbacks// if this happens outside render or commit phase (e.g. in an event).flushSyncCallbacks();}});}}newCallbackNode = null;} else {var schedulerPriorityLevel;switch (lanesToEventPriority(nextLanes)) {case DiscreteEventPriority:schedulerPriorityLevel = ImmediatePriority;break;case ContinuousEventPriority:schedulerPriorityLevel = UserBlockingPriority;break;case DefaultEventPriority:schedulerPriorityLevel = NormalPriority;break;case IdleEventPriority:schedulerPriorityLevel = IdlePriority;break;default:schedulerPriorityLevel = NormalPriority;break;}newCallbackNode = scheduleCallback$1(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));}root.callbackPriority = newCallbackPriority;root.callbackNode = newCallbackNode;
} // This is the entry point for every concurrent task, i.e. anything that
// goes through Scheduler.

四、performSyncWorkOnRoot函数

以同步方式执行根节点的渲染和提交流程,用于处理最高优先级(SyncLane)的更新。

function performSyncWorkOnRoot(root) {{syncNestedUpdateFlag();}if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {throw new Error('Should not already be working.');}flushPassiveEffects();var lanes = getNextLanes(root, NoLanes);if (!includesSomeLane(lanes, SyncLane)) {// There's no remaining sync work left.ensureRootIsScheduled(root, now());return null;}var exitStatus = renderRootSync(root, lanes);if (root.tag !== LegacyRoot && exitStatus === RootErrored) {// If something threw an error, try rendering one more time. We'll render// synchronously to block concurrent data mutations, and we'll includes// all pending updates are included. If it still fails after the second// attempt, we'll give up and commit the resulting tree.var errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);if (errorRetryLanes !== NoLanes) {lanes = errorRetryLanes;exitStatus = recoverFromConcurrentError(root, errorRetryLanes);}}if (exitStatus === RootFatalErrored) {var fatalError = workInProgressRootFatalError;prepareFreshStack(root, NoLanes);markRootSuspended$1(root, lanes);ensureRootIsScheduled(root, now());throw fatalError;}if (exitStatus === RootDidNotComplete) {throw new Error('Root did not complete. This is a bug in React.');} // We now have a consistent tree. Because this is a sync render, we// will commit it even if something suspended.var finishedWork = root.current.alternate;root.finishedWork = finishedWork;root.finishedLanes = lanes;commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions); // Before exiting, make sure there's a callback scheduled for the next// pending level.ensureRootIsScheduled(root, now());return null;
}function flushRoot(root, lanes) {if (lanes !== NoLanes) {markRootEntangled(root, mergeLanes(lanes, SyncLane));ensureRootIsScheduled(root, now());if ((executionContext & (RenderContext | CommitContext)) === NoContext) {resetRenderTimer();flushSyncCallbacks();}}
}

五、performConcurrentWorkOnRoot函数

并发渲染的核心入口,负责调度和执行并发模式下的 Fiber 树渲染。

function performConcurrentWorkOnRoot(root, didTimeout) {{resetNestedUpdateFlag();} // Since we know we're in a React event, we can clear the current// event time. The next update will compute a new event time.currentEventTime = NoTimestamp;currentEventTransitionLane = NoLanes;if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {throw new Error('Should not already be working.');} // Flush any pending passive effects before deciding which lanes to work on,// in case they schedule additional work.var originalCallbackNode = root.callbackNode;var didFlushPassiveEffects = flushPassiveEffects();if (didFlushPassiveEffects) {// Something in the passive effect phase may have canceled the current task.// Check if the task node for this root was changed.if (root.callbackNode !== originalCallbackNode) {// The current task was canceled. Exit. We don't need to call// `ensureRootIsScheduled` because the check above implies either that// there's a new task, or that there's no remaining work on this root.return null;}} // Determine the next lanes to work on, using the fields stored// on the root.var lanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);if (lanes === NoLanes) {// Defensive coding. This is never expected to happen.return null;} // We disable time-slicing in some cases: if the work has been CPU-bound// for too long ("expired" work, to prevent starvation), or we're in// sync-updates-by-default mode.// TODO: We only check `didTimeout` defensively, to account for a Scheduler// bug we're still investigating. Once the bug in Scheduler is fixed,// we can remove this, since we track expiration ourselves.var shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && ( !didTimeout);var exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);if (exitStatus !== RootInProgress) {if (exitStatus === RootErrored) {// If something threw an error, try rendering one more time. We'll// render synchronously to block concurrent data mutations, and we'll// includes all pending updates are included. If it still fails after// the second attempt, we'll give up and commit the resulting tree.var errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);if (errorRetryLanes !== NoLanes) {lanes = errorRetryLanes;exitStatus = recoverFromConcurrentError(root, errorRetryLanes);}}if (exitStatus === RootFatalErrored) {var fatalError = workInProgressRootFatalError;prepareFreshStack(root, NoLanes);markRootSuspended$1(root, lanes);ensureRootIsScheduled(root, now());throw fatalError;}if (exitStatus === RootDidNotComplete) {// The render unwound without completing the tree. This happens in special// cases where need to exit the current render without producing a// consistent tree or committing.//// This should only happen during a concurrent render, not a discrete or// synchronous update. We should have already checked for this when we// unwound the stack.markRootSuspended$1(root, lanes);} else {// The render completed.// Check if this render may have yielded to a concurrent event, and if so,// confirm that any newly rendered stores are consistent.// TODO: It's possible that even a concurrent render may never have yielded// to the main thread, if it was fast enough, or if it expired. We could// skip the consistency check in that case, too.var renderWasConcurrent = !includesBlockingLane(root, lanes);var finishedWork = root.current.alternate;if (renderWasConcurrent && !isRenderConsistentWithExternalStores(finishedWork)) {// A store was mutated in an interleaved event. Render again,// synchronously, to block further mutations.exitStatus = renderRootSync(root, lanes); // We need to check again if something threwif (exitStatus === RootErrored) {var _errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);if (_errorRetryLanes !== NoLanes) {lanes = _errorRetryLanes;exitStatus = recoverFromConcurrentError(root, _errorRetryLanes); // We assume the tree is now consistent because we didn't yield to any// concurrent events.}}if (exitStatus === RootFatalErrored) {var _fatalError = workInProgressRootFatalError;prepareFreshStack(root, NoLanes);markRootSuspended$1(root, lanes);ensureRootIsScheduled(root, now());throw _fatalError;}} // We now have a consistent tree. The next step is either to commit it,// or, if something suspended, wait to commit it after a timeout.root.finishedWork = finishedWork;root.finishedLanes = lanes;finishConcurrentRender(root, exitStatus, lanes);}}ensureRootIsScheduled(root, now());if (root.callbackNode === originalCallbackNode) {// The task node scheduled for this root is the same one that's// currently executed. Need to return a continuation.return performConcurrentWorkOnRoot.bind(null, root);}return null;
}

六、prepareFreshStack函数

prepareFreshStack的作用是为一次新的 Fiber 树渲染初始化全局状态,主要包括:

  • 重置当前 root 的渲染相关状态(如 finishedWork、finishedLanes 等)。
  • 取消上一次渲染遗留的超时(timeout)。
  • 如果上一次渲染被中断,清理未完成的 workInProgress。
  • 创建新的 workInProgress Fiber 树(即 root.current 的 alternate),并将全局变量 workInProgress指向它。
  • 设置本次渲染的 lanes(优先级)。
  • 重置本次渲染相关的全局变量(如 workInProgressRoot、workInProgressRootRenderLanes 等)。
  • 清空上次渲染遗留的错误、跳过的 lanes、pinged lanes 等。
function prepareFreshStack(root, lanes) {root.finishedWork = null;root.finishedLanes = NoLanes;var timeoutHandle = root.timeoutHandle;if (timeoutHandle !== noTimeout) {// The root previous suspended and scheduled a timeout to commit a fallback// state. Now that we have additional work, cancel the timeout.root.timeoutHandle = noTimeout; // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check abovecancelTimeout(timeoutHandle);}if (workInProgress !== null) {var interruptedWork = workInProgress.return;while (interruptedWork !== null) {var current = interruptedWork.alternate;unwindInterruptedWork(current, interruptedWork);interruptedWork = interruptedWork.return;}}workInProgressRoot = root;var rootWorkInProgress = createWorkInProgress(root.current, null);workInProgress = rootWorkInProgress;workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;workInProgressRootExitStatus = RootInProgress;workInProgressRootFatalError = null;workInProgressRootSkippedLanes = NoLanes;workInProgressRootInterleavedUpdatedLanes = NoLanes;workInProgressRootPingedLanes = NoLanes;workInProgressRootConcurrentErrors = null;workInProgressRootRecoverableErrors = null;finishQueueingConcurrentUpdates();{ReactStrictModeWarnings.discardPendingWarnings();}return rootWorkInProgress;
}

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

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

相关文章

Unity3D + VS2022连接雷电模拟器调试

本文参考了Unity3D Profiler 连接真机和模拟器_unity 连接雷电模拟器-CSDN博客 具体步骤: 1、cmd打开命令窗口,输入adb devices,确认能检测到模拟器 示例:List of devices attached emulator-5554 device 2、…

学习软件测试的第十五天

1.会写测试用例吗?测试用例有什么要素“会的,我写过多个功能测试和接口测试的测试用例。我写用例的时候会根据需求文档或原型图分析测试点,然后从正常流程、异常流程、边界情况等方面设计测试场景。每条用例我都会包含:用例编号、…

C++硬实时调度:原理、实践与最佳方案

在工业自动化、航空航天、医疗设备等领域,系统的实时性往往直接关系到生命安全和财产损失。C作为高性能编程语言,为硬实时系统开发提供了强大支持。本文将深入探讨C硬实时调度的核心技术,从操作系统原理到代码实现的全方位解析。 一、实时系统…

LeetCode 1156.单字符重复子串的最大长度

如果字符串中的所有字符都相同,那么这个字符串是单字符重复的字符串。 给你一个字符串 text,你只能交换其中两个字符一次或者什么都不做,然后得到一些单字符重复的子串。返回其中最长的子串的长度。 示例 1: 输入:text…

K近邻算法的分类与回归应用场景

K近邻算法的分类与回归应用场景 K近邻(K-Nearest Neighbors, KNN)算法是一种基础但强大的机器学习方法,它既可以用于分类问题,也能解决回归问题。 两者的核心思想都是基于"近朱者赤,近墨者黑"的原理&#xf…

算法精讲--正则表达式(二):分组、引用与高级匹配技术

算法精讲–正则表达式(二):分组、引用与高级匹配技术 🚀正则表达式的真正力量在于组合使用各种语法元素,创造出强大而精确的匹配模式! —— 作者:无限大 推荐阅读时间:25 分钟 适用人…

python+requests 接口自动化测试实战

首先介绍一下python的requests模块: requests的使用介绍:requests快速入门 Python结合requests库实现接口自动化测试环境说明: 1.WIN 7, 64位 2.Python3.4.3 (pip-8.1.2) 3.Requests —>pip install requests 4.U…

NAT 实验

NAT 实验 一.实验拓扑图实验目的 1.按照图示配置 IP 地址 2.私网 A 通过 R1 接入到互联网,私网 B 通过 R3 接入到互联网 3.私网 A 内部存在 Vlan10 和 Vlan20,通过 R1 上单臂路由访问外部网络 4.私网 A 通过 NAPT 使 Vlan10 和 Vlan20 都能够使用 R1 的公…

buuctf——web刷题第三页

第三页 目录 [FBCTF2019]RCEService [0CTF 2016]piapiapia [Zer0pts2020]Can you guess it? [WUSTCTF2020]颜值成绩查询 [SUCTF 2019]Pythonginx [MRCTF2020]套娃 [CSCCTF 2019 Qual]FlaskLight [watevrCTF-2019]Cookie Store [WUSTCTF2020]CV Maker [红明谷CTF 202…

前后端分离项目中的接口设计与调用流程——以高仙机器人集成为例

一、背景介绍在前后端分离项目开发中,前端页面需要频繁调用后端接口获取数据。在高仙机器人对接项目中,我们采用了若依(RuoYi)框架,前端通过统一的 API 封装与后端进行数据交互,而后端再对接高仙官方的 OPE…

【第五节】部署http接口到ubuntu server上的docker内

描述清楚需求,让deepseek帮我们写一个demo,文件结构如下 FLASK_API_001 ├── app.py └── Dockerfile └── requirements.txtapp.pyfrom flask import Flask, jsonify, requestapp Flask(__name__)# 根路由 app.route(/) def home():return "…

在 IntelliJ IDEA 中添加框架支持的解决方案(没有出现Add Framework Support)

在 IntelliJ IDEA 中添加框架支持的解决方案 问题背景 版本变化:在 IntelliJ IDEA 2023.2 及更高版本中,项目右键菜单中的 “Add Framework Support” 选项已被移除。 常见需求:为 Java 项目添加框架支持(如 Maven、Spring 等&am…

北京-4年功能测试2年空窗-报培训班学测开-第五十天

咦,昨天路上写一半就到家了,后来想早点睡就忘了还要发了,现在赶紧补上昨天是最后一节课(我们将一整天的课称为一节),这就结课了昨天讲了简历编写,面试要准备的内容,还有redis和docker也没有什么…

华为鸿蒙HarmonyOpenEye项目:开眼App的鸿蒙实现之旅

华为鸿蒙HarmonyOpenEye项目:开眼App的鸿蒙实现之旅 引言 在当今移动应用开发的浪潮中,鸿蒙系统凭借其独特的分布式能力和高效的开发框架,吸引了众多开发者的目光。今天要给大家介绍的是一个基于华为鸿蒙系统开发的开眼App项目——HarmonyO…

代码随想录day36dp4

文章目录1049.最后一块石头的重量II494.目标和474.一和零1049.最后一块石头的重量II 题目链接 文章讲解 class Solution { public:int lastStoneWeightII(vector<int>& stones) {// 1. 确定 DP 数组及下标的含义&#xff1a;// dp[i][j] 表示考虑前 i 块石头&#…

Python 爬虫实战指南:按关键字搜索商品

在电商领域&#xff0c;按关键字搜索商品并获取其详情信息是一项常见的需求。无论是进行市场调研、竞品分析还是用户体验优化&#xff0c;能够快速准确地获取商品信息都至关重要。1688 作为国内领先的 B2B 电商平台&#xff0c;提供了丰富的商品资源。本文将详细介绍如何使用 P…

【源力觉醒 创作者计划】百度AI的开放新篇章:文心4.5本地化部署指南与未来生态战略展望

百度AI的开放新篇章&#xff1a;文心4.5本地化部署指南与未来生态战略展望 一起来玩转文心大模型吧&#x1f449;文心大模型免费下载地址&#xff1a;https://ai.gitcode.com/theme/1939325484087291906 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30…

测试工作中的质量门禁管理

一、前言 测试阶段的质量门禁设计要考虑几个维度,首先是研发流程的阶段划分,每个阶段都要有明确的准入准出标准;其次要考虑不同测试类型的特点,比如功能测试和性能测试的验收标准肯定不同;最后还要平衡质量要求和项目进度。 在单元测试阶段,可以设置通过率和覆盖率的阈值…

线上分享:解码eVTOL安全基因,构建安全飞行生态

随着城市空中交通&#xff08;UAM&#xff09;快速发展&#xff0c;电动垂直起降飞行器&#xff08;eVTOL&#xff09;面临严格的安全与可靠性要求&#xff0c;需满足全球适航标准及全生命周期分析。安全与可靠的飞行系统成为行业关注的焦点。在此背景下&#xff0c;本期线上分…

C回调函数基础用法

&#x1f4cc; 定义&#xff1a;回调函数是通过函数指针传递给另一个函数的函数&#xff0c;这个被传进去的函数将在某个时刻被“回调”调用。换句话说&#xff1a;你定义一个函数 A把函数 A 的地址&#xff08;即函数指针&#xff09;作为参数传给函数 B函数 B 在合适的时机调…