React19源码系列之 root.render过程
在创建react项目的时候,入口文件总是有这样一行代码
root.render(<App />)
所以 root.render() 执行是怎样的? 下面就来看看。
之前的文章就提及,root是一个 ReactDOMRoot 对象,其原型链上有 render 和 unmount 方法。
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =// $FlowFixMe[missing-this-annot]function (children: ReactNodeList): void {// this就是ReactDOMRoot实例,// this._internalRoot就是fiberRoot对象const root = this._internalRoot;if (root === null) {throw new Error('Cannot update an unmounted root.');}//执行更新updateContainer(children, root, null, null);};
root.render流程图
updateContainer
updateContainer 函数是 React 中用于触发更新容器内容的核心函数,它的主要作用是启动一个更新流程,将 新的 React 元素(element)渲染到 指定的容器(container)中。该函数会获取当前的 根 Fiber 节点,请求一个更新车道(lane)来确定更新的优先级,然后调用 updateContainerImpl 函数执行具体的更新操作,最后返回更新车道。
函数参数含义
- element:类型为 ReactNodeList,表示要渲染到容器中的 React 元素或元素列表。它可以是单个 React 元素,也可以是多个元素组成的数组。
- container:类型为 OpaqueRoot,是一个不透明的根对象,实际上代表了 FiberRootNode,包含了整个 React 应用的根节点信息。
- parentComponent:类型为 ?React$Component<any, any>,是一个可选的父组件。在某些情况下,可能需要指定父组件来进行更新操作,但通常为 null。
- callback:类型为 ?Function,是一个可选的回调函数,在更新完成后会被调用。
function updateContainer(element: ReactNodeList,container: OpaqueRoot,parentComponent: ?React$Component<any, any>,// nullcallback: ?Function,// null
): Lane {//container 是 FiberRootNode 对象,container.current 指向当前的根 Fiber 节点。这个根 Fiber 节点代表了当前的渲染状态,是整个 React 应用的起点。const current = container.current;//同步直接返回 `SyncLane` = 1。以后开启并发和异步等返回的值就不一样了,目前只有同步这个模式//请求 根Fiber 更新车道(lane),用于确定更新的优先级。const lane = requestUpdateLane(current);// 负责执行具体的更新逻辑。它会根据传入的根 Fiber 节点、更新车道、新的 React 元素、容器、父组件和回调函数,进行协调(reconciliation)操作,比较新旧元素的差异,并决定如何更新 DOM 以反映新的元素。updateContainerImpl(current,// 根fiberlane,// 更新车道element,// 子节点container,// fiberRootparentComponent,// nullcallback,);return lane;
}
updateContainerImpl
updateContainerImpl 函数是 React 中处理容器更新的具体实现函数,其主要任务是在接收到更新请求后,对更新进行一系列的准备工作,包括标记性能信息、获取上下文、创建更新对象、将更新对象入队,最后调度更新任务以开始执行更新操作。
函数参数含义:
- rootFiber:类型为 Fiber,代表 React 应用的根 Fiber 节点,是整个更新操作的起始点。
- lane:类型为 Lane,表示本次更新的优先级车道,用于决定更新任务的执行顺序和优先级。
- element:类型为 ReactNodeList,是需要更新到容器中的 React 元素或元素列表。
- container:类型为 OpaqueRoot,是一个不透明的根对象,通常代表 FiberRootNode,包含了整个 React 应用的根节点信息。
- parentComponent:类型为 ?React$Component<any, any>,是可选的父组件,用于获取上下文。
- callback:类型为 ?Function,是可选的回调函数,在更新完成后会被调用。
function updateContainerImpl(rootFiber: Fiber,// 根节点fiberlane: Lane,// 更新优先级车道element: ReactNodeList,// 需要更新的react元素container: OpaqueRoot,//FiberRootNodeparentComponent: ?React$Component<any, any>,callback: ?Function,
): void {//获取当前节点和子节点的上下文const context = getContextForSubtree(parentComponent);if (container.context === null) {// 将获取到的上下文赋值给 container.contextcontainer.context = context;} else {// 将获取到的上下文赋值给 container.pendingContextcontainer.pendingContext = context;}//创建一个 update 更新对象const update = createUpdate(lane);// 记录update的载荷信息update.payload = {element};// 如果有回调信息,保存callback = callback === undefined ? null : callback;if (callback !== null) {// 存储callbackupdate.callback = callback;}// 将创建好的更新对象 update 加入到根 Fiber 节点的更新队列中。该函数返回根 FiberRootNode 对象。// root 为FiberRootconst root = enqueueUpdate(rootFiber, update, lane);// 调度更新任务if (root !== null) {// 启动一个与当前更新车道相关的计时器,用于记录更新操作的时间。// startUpdateTimerByLane(lane);// 根据更新的优先级车道安排更新任务的执行顺序。scheduleUpdateOnFiber(root, rootFiber, lane);// 处理过渡(transitions)相关的逻辑,确保过渡效果的正确执行。// entangleTransitions(root, rootFiber, lane);}}
工具函数 createUpdate
createUpdate 函数的主要作用是创建一个更新对象(Update),该对象用于描述 React 组件状态或属性的更新操作。在 React 的更新机制中,更新操作会被封装成一个个 Update 对象,然后被添加到更新队列中,后续会根据这些更新对象来计算新的状态并更新组件。
function createUpdate(lane: Lane): Update<mixed> {const update: Update<mixed> = {lane,// 优先级车道tag: UpdateState,// 更新类型payload: null,// payload 用于存储更新操作的具体数据,例如新的状态值、属性值等。在后续的更新过程中,会根据 payload 的内容来计算新的状态。callback: null,// 回调,更新完成后执行next: null,// next 是一个指向链表中下一个更新对象的指针。在 React 中,更新对象会以链表的形式存储在更新队列中,通过 next 指针可以将多个更新对象连接起来。};return update;
}
// 表示普通状态的更新,例如hook更新
export const UpdateState = 0;// 表示替换状态的操作。当使用 ReplaceState 类型的更新时,会直接用新的状态对象替换当前的状态对象,而不是像 UpdateState 那样合并状态。
export const ReplaceState = 1;// 表示强制更新操作。当调用 forceUpdate 方法(类组件)时,会触发 ForceUpdate 类型的更新。强制更新会绕过状态和属性的浅比较,直接触发组件的重新渲染。
export const ForceUpdate = 2;// CaptureUpdate 通常与错误边界和捕获阶段的更新有关。在 React 的错误处理机制中,捕获阶段可以捕获子组件抛出的错误,并进行相应的处理。CaptureUpdate 可能用于在捕获阶段触发的状态更新操作。
export const CaptureUpdate = 3;
工具函数之 enqueueUpdate 函数
enqueueUpdate
函数的主要作用是将一个更新对象 update
加入到指定 Fiber
节点的更新队列中。更新队列用于存储组件的状态更新操作,在后续的渲染过程中,React 会根据这些更新操作来计算新的状态。该函数会根据 Fiber
节点的状态和更新阶段,选择不同的方式将更新对象加入队列,并标记从该 Fiber
节点到根节点的更新车道,最后返回根 Fiber
节点。
函数参数含义
fiber
:类型为Fiber
,代表要加入更新的Fiber
节点,Fiber
是 React Fiber 架构中的核心数据结构,每个Fiber
节点对应一个组件实例。update
:类型为Update<State>
,是一个更新对象,包含了更新的具体信息,如更新的类型、载荷等。lane
:类型为Lane
,表示更新的优先级车道,用于确定更新的执行顺序和优先级。
function enqueueUpdate<State>(fiber: Fiber,update: Update<State>,lane: Lane,
): FiberRoot | null {// 根fiber的更新队列const updateQueue = fiber.updateQueue;// 如果 updateQueue 为空,说明该 fiber 已经被卸载,直接返回 null。if (updateQueue === null) {// fiber 被卸载时return null;}// 从 updateQueue 中获取共享队列 sharedQueue。sharedQueue 是一个对象,包含三个属性:interleaved、lanes 和 pending。返回一个对象 {interleaved:null, lanes:0, pending:null}const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;// 如果 fiber 处于不安全的类渲染阶段if (isUnsafeClassRenderPhaseUpdate(fiber)) {// pending 永远指向最后一个更新const pending = sharedQueue.pending;// 如果 pending 为空,说明这是第一个更新,需要创建一个循环单链表,将 update.next 指向 update 自己。if (pending === null) {update.next = update;} else {// 如果 pending 不为空,取出第一个更新并插入新的更新,使其成为循环单链表的一部分。update.next = pending.next;pending.next = update;}// 更新 sharedQueue.pending 指向新的 update。sharedQueue.pending = update;// 调用 unsafe_markUpdateLaneFromFiberToRoot 标记更新从 fiber 到根 Fiber,并返回根 Fiber。return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);} else {// 调用 enqueueConcurrentClassUpdate 函数处理并发类更新。该函数会根据并发更新的规则将更新对象加入队列,并进行相应的处理return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);}
}
在初始的状态,sharedQueue.pending
为 null。第一次更新的时候 sharedQueue.pending
指向 update,并且 update.next 指向 update 自己,形成一个循环链表。
第二次更新的时候 sharedQueue.pending
更新为指向 update(新传入的),update.next 指向 update1(原来的pending.update),update1.next 指向 update,形成新的循环链表。
type Update<State> = {lane: Lane,tag: 0 | 1 | 2 | 3,payload: any,callback: (() => mixed) | null,next: Update<State> | null,
};
工具函数之 enqueueConcurrentClassUpdate
将一个并发类更新对象 update
加入到指定 Fiber
节点的并发更新队列中,并返回该 Fiber
节点对应的根 Fiber
节点。此函数用于处理 React 并发模式下的类组件更新,确保更新操作能被正确地加入队列并关联到根节点
function enqueueConcurrentClassUpdate<State>(fiber: Fiber,queue: ClassQueue<State>,// 共享队列update: ClassUpdate<State>,// 更新对象lane: Lane,
): FiberRoot | null {// 并发更新队列const concurrentQueue: ConcurrentQueue = (queue: any);// 并发更新对象const concurrentUpdate: ConcurrentUpdate = (update: any);// 与前面的enqueueUpdate不同// 将转换后的并发更新队列 concurrentQueue 和并发更新对象 concurrentUpdate 加入到指定 Fiber 节点的更新队列中,并指定更新的优先级车道 lane。enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);// 获取 FiberRoot 节点return getRootForUpdatedFiber(fiber);
}
const concurrentQueues: Array<any> = [];
// concurrentQueuesIndex 是一个全局索引,用于记录当前存储位置。
let concurrentQueuesIndex = 0;// concurrentlyUpdatedLanes 是一个全局变量,用于记录所有并发更新的车道集合。
let concurrentlyUpdatedLanes: Lanes = NoLanes;function enqueueUpdate(fiber: Fiber,queue: ConcurrentQueue | null,// 更新队列update: ConcurrentUpdate | null,// 更新对象lane: Lane,
) {// concurrentQueues 是一个全局数组,用于临时存储所有并发更新的相关信息。concurrentQueues[concurrentQueuesIndex++] = fiber;concurrentQueues[concurrentQueuesIndex++] = queue;concurrentQueues[concurrentQueuesIndex++] = update;concurrentQueues[concurrentQueuesIndex++] = lane;concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);// 更新fiber节点的lanesfiber.lanes = mergeLanes(fiber.lanes, lane); // fiber.lanes | lanesconst alternate = fiber.alternate;if (alternate !== null) {// 更新alternate的lanesalternate.lanes = mergeLanes(alternate.lanes, lane);}
}
工具函数之 getRootForUpdatedFiber
从一个被更新的 Fiber
节点出发,向上遍历找到对应的根 Fiber
节点(FiberRoot
)。
function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
// sourceFiber表示需要查找根节点的起始 Fiber 节点。// 检测是否在已卸载的 Fiber 节点上进行更新。// detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);// 根fiberlet node = sourceFiber;// 父节点let parent = node.return;// 向上遍历 Fiber 树while (parent !== null) {// 检测是否在已卸载的 Fiber 节点上进行更新。// detectUpdateOnUnmountedFiber(sourceFiber, node);node = parent;parent = node.return;}return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
}
scheduleUpdateOnFiber
其主要功能是为指定的 Fiber
节点安排一个更新任务。该函数会根据当前的渲染状态、更新的优先级以及其他相关条件,对更新任务进行不同的处理,确保更新能够正确、高效地被调度和执行。
root
:类型为FiberRoot
,代表 React 应用的根节点,是整个Fiber
树的根。fiber
:类型为Fiber
,表示需要进行更新的具体Fiber
节点。lane
:类型为Lane
,代表此次更新的优先级车道。
function scheduleUpdateOnFiber(root: FiberRoot,fiber: Fiber,lane: Lane,) {// 检查当前 root 是否处于挂起状态。
// 若根节点 root 是正在进行渲染的根节点 workInProgressRoot,并且渲染因数据未就绪而挂起(workInProgressSuspendedReason === SuspendedOnData),或者根节点有未完成的提交需要取消(root.cancelPendingCommit !== null)if ((root === workInProgressRoot && workInProgressSuspendedReason === SuspendedOnData) ||root.cancelPendingCommit !== null) {// 调用 prepareFreshStack 函数为 root 准备一个新的渲染栈prepareFreshStack(root, NoLanes);// 用于标记是否尝试对整个树进行渲染操作,这里设置为 false 表示没有尝试对整个树进行渲染。const didAttemptEntireTree = false;// 调用 markRootSuspended 函数标记根节点为挂起状态,同时传入相关的渲染车道和是否尝试对整个树进行渲染的标记markRootSuspended(root,// 要标记的根 Fiber 节点。workInProgressRootRenderLanes,// 正在进行渲染的根节点的渲染车道,用于表示渲染的优先级。workInProgressDeferredLane,// 正在进行的延迟车道,可能与延迟渲染或更新相关。didAttemptEntireTree,// 标记是否尝试对整个树进行渲染。);}// 调用 markRootUpdated 函数,标记根节点有一个挂起的更新,并且记录更新的优先级车道 lane。markRootUpdated(root, lane);// 检查当前是否处于渲染阶段,并且更新的 root 是正在进行渲染的 root。if ((executionContext & RenderContext) !== NoLanes && root === workInProgressRoot) {// Track lanes that were updated during the render phase// 调用 mergeLanes 函数将当前更新的车道 lane 合并到 workInProgressRootRenderPhaseUpdatedLanes 中,记录渲染阶段的更新车道workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(workInProgressRootRenderPhaseUpdatedLanes,lane,);} else {// 处理正常更新(非渲染阶段)if (root === workInProgressRoot) {// 如果更新的 root 是正在进行渲染的 root,且当前不在渲染阶段,将更新的车道 lane 合并到 workInProgressRootInterleavedUpdatedLanes 中。if ((executionContext & RenderContext) === NoContext) {workInProgressRootInterleavedUpdatedLanes = mergeLanes(workInProgressRootInterleavedUpdatedLanes,lane,);}// 如果 root 处于延迟挂起状态,再次标记 root 为挂起状态。if (workInProgressRootExitStatus === RootSuspendedWithDelay) {const didAttemptEntireTree = false;markRootSuspended(root,workInProgressRootRenderLanes,workInProgressDeferredLane,didAttemptEntireTree,);}}// 调用 ensureRootIsScheduled 函数确保根节点被正确调度。ensureRootIsScheduled(root);}
}
ensureRootIsScheduled
ensureRootIsScheduled
函数的主要作用是确保 FiberRoot
节点被正确调度。它会将 FiberRoot
节点添加到调度列表中,标记可能存在待处理的同步工作,安排微任务来处理根节点的调度,并且根据配置决定是否在微任务期间为根节点安排任务。
let firstScheduledRoot: FiberRoot | null = null;
let lastScheduledRoot: FiberRoot | null = null;
let didScheduleMicrotask: boolean = false;function ensureRootIsScheduled(root: FiberRoot): void {// 首先检查 root 是否已经在调度列表中。// 如果 root 等于 lastScheduledRoot 或者 root.next 不为 null,说明该根节点已经被调度,直接跳过后续添加操作。if (root === lastScheduledRoot || root.next !== null) {// Fast path. This root is already scheduled.} else {// 若 lastScheduledRoot 为 null,表示调度列表为空,将 firstScheduledRoot 和 lastScheduledRoot 都设置为 root,即该根节点成为调度列表中的第一个也是最后一个节点。if (lastScheduledRoot === null) {firstScheduledRoot = lastScheduledRoot = root;} else {// 若 lastScheduledRoot 不为 null,将 lastScheduledRoot.next 设置为 root,并将 lastScheduledRoot 更新为 root,即将该根节点添加到调度列表的末尾。lastScheduledRoot.next = root;lastScheduledRoot = root;}}// 将 mightHavePendingSyncWork 标记为 true,表示可能存在待处理的同步工作。这可能会影响后续的调度决策,例如在某些情况下需要优先处理同步工作。mightHavePendingSyncWork = true;// 检查 didScheduleMicrotask 标志,如果为 false,说明还没有安排微任务来处理根节点的调度。if (!didScheduleMicrotask) {// 将 didScheduleMicrotask 设置为 true,表示已经安排了微任务。didScheduleMicrotask = true;// 用 scheduleImmediateTask 函数,安排一个立即执行的微任务,执行 processRootScheduleInMicrotask 函数,该函数可能会处理根节点的调度逻辑。scheduleImmediateTask(processRootScheduleInMicrotask);}// 检查 enableDeferRootSchedulingToMicrotask 配置项,如果为 false,表示不延迟根节点的调度到微任务中。if (!enableDeferRootSchedulingToMicrotask) {// 调用 scheduleTaskForRootDuringMicrotask 函数,在微任务期间为根节点安排任务,now() 函数返回当前时间,可能用于确定任务的调度时间。scheduleTaskForRootDuringMicrotask(root, now());}
}
scheduleImmediateTask(processRootScheduleInMicrotask) 立即执行的微任务。即当当前宏任务执行完毕后立即执行。
工具函数之 scheduleImmediateTask
scheduleImmediateTask
函数的作用是安排一个立即执行的任务。它会根据当前环境是否支持微任务(microtasks)来选择合适的执行方式
function scheduleImmediateTask(cb: () => mixed) {// 参数cb为立即执行的任务// 支持微任务if (supportsMicrotasks) {// 把传入的回调函数安排到微任务队列中。当宏任务处理完后在再次执行这里scheduleMicrotask(() => {// 获取当前的执行上下文。const executionContext = getExecutionContext();// 检查当前执行上下文是否处于渲染(RenderContext)或提交(CommitContext)阶段。if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {// 处于渲染阶段或提交阶段, 安排回调函数的执行Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb);return;}// 执行回调函数cb();});} else {// If microtasks are not supported, use Scheduler.// 安排回调函数的执行Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb);}
}
const scheduleMicrotask: any =typeof queueMicrotask === 'function'? queueMicrotask: typeof localPromise !== 'undefined'? callback =>localPromise.resolve(null).then(callback).catch(handleErrorInNextTick): scheduleTimeout; // TODO: Determine the best fallback here.
const localPromise = typeof Promise === 'function' ? Promise : undefined;
Window:queueMicrotask() 方法 - Web API | MDN
工具函数之 scheduleCallback
unstable_scheduleCallback
函数是 React 调度机制中的核心函数,用于根据任务的优先级和延迟时间,将任务分配到不同的队列(taskQueue
或 timerQueue
)中,并调度相应的宿主回调(如浏览器事件循环)来执行任务。其核心逻辑包括:
- 根据优先级和延迟时间计算任务的开始时间和过期时间。
- 将延迟任务加入
timerQueue
,非延迟任务加入taskQueue
。 - 调度宿主超时(
requestHostTimeout
)或回调(requestHostCallback
)以触发任务执行。
const scheduleCallback = Scheduler.unstable_scheduleCallback;
// 待执行任务队列
var taskQueue: Array<Task> = [];// 定时器任务队列
var timerQueue: Array<Task> = [];function unstable_scheduleCallback(priorityLevel: PriorityLevel,// 任务优先级callback: Callback,// 回调函数options?: {delay: number},// delay 延迟任务的毫秒
): Task {// var currentTime = getCurrentTime();// 定义任务开始时间var startTime;if (typeof options === 'object' && options !== null) {var delay = options.delay;if (typeof delay === 'number' && delay > 0) {// 任务开始时间, 当前时间加上延迟时间startTime = currentTime + delay;} else {startTime = currentTime;}} else {startTime = currentTime;}// 根据优先级设置超时时间var timeout;// 车道优先级switch (priorityLevel) {case ImmediatePriority:// 立即超市// Times out immediatelytimeout = -1;break;case UserBlockingPriority:// Eventually times outtimeout = userBlockingPriorityTimeout;break;case IdlePriority:// 永不超时// Never times outtimeout = maxSigned31BitInt;break;case LowPriority:// Eventually times outtimeout = lowPriorityTimeout;break;case NormalPriority:default:// Eventually times outtimeout = normalPriorityTimeout;break;}// 过期时间 var expirationTime = startTime + timeout;// 创建新任务对象var newTask: Task = {id: taskIdCounter++, // 任务IDcallback,// 回调函数priorityLevel,// 任务优先级startTime,// 开始时间expirationTime,// 过期时间sortIndex: -1,// 排序索引};// 根据开始时间加入不同队列// 延迟任务(startTime > currentTime)if (startTime > currentTime) {// This is a delayed task.// 将任务的排序索引设置为开始时间。newTask.sortIndex = startTime;// 将任务加入 timerQueue(延迟任务队列)。push(timerQueue, newTask);// 如果 taskQueue 为空 且 当前任务是 timerQueue 中最早的延迟任务if (peek(taskQueue) === null && newTask === peek(timerQueue)) {// All tasks are delayed, and this is the task with the earliest delay.// 标记是否已经安排了一个主机超时任务。if (isHostTimeoutScheduled) {// Cancel an existing timeout.cancelHostTimeout();} else {isHostTimeoutScheduled = true;}// Schedule a timeout.// requestHostTimeout设置一个定时器任务// handleTimeout 是超时后需要执行的回调函数,通常用于处理延迟任务队列中的任务。// startTime - currentTime 表示从当前时间到新任务开始执行的时间间隔,即需要等待的时间。requestHostTimeout(handleTimeout, startTime - currentTime);}} else {// 非延迟任务(startTime <= currentTime)//将任务的排序索引设置为过期时间。newTask.sortIndex = expirationTime;// 将任务加入 taskQueue(待执行任务队列)。push(taskQueue, newTask);// 如果没有正在调度的宿主回调,并且当前没有正在执行的工作,则安排一个宿主回调。if (!isHostCallbackScheduled && !isPerformingWork) {isHostCallbackScheduled = true;requestHostCallback();}}return newTask;
}
requestHostCallback
函数是 React 调度系统中与宿主环境(通常指浏览器)交互的关键函数之一,其主要作用是请求宿主环境调度执行任务。该函数会检查消息循环是否正在运行,如果没有运行,则启动消息循环,并安排执行任务直到达到截止时间。
function requestHostCallback() {if (!isMessageLoopRunning) {isMessageLoopRunning = true;schedulePerformWorkUntilDeadline();}
}
flushWork
flushWork
函数是 React 任务调度系统中的核心执行函数,主要用于触发任务的实际执行。它会在任务队列准备好后,调用 workLoop
函数循环处理任务,直到没有可执行的任务或需要向宿主环境让步。同时,该函数还负责管理任务执行的状态标记(如是否正在执行、是否需要调度宿主回调等),确保任务执行流程的正确性和完整性。
function flushWork(initialTime: number) {// 将 “是否已安排宿主回调” 的标记置为 false。这意味着本次任务执行完毕后,下次再有任务时需要重新调度宿主回调(如通过 requestHostCallback)。isHostCallbackScheduled = false;// 如果存在已安排的超时任务(如延迟执行的定时器任务),但当前需要执行的是立即任务(非延迟任务),则取消超时任务。if (isHostTimeoutScheduled) {// We scheduled a timeout but it's no longer needed. Cancel it.isHostTimeoutScheduled = false;cancelHostTimeout();}// 用于防止任务执行过程中被重复调度,确保同一时间只有一个任务循环在运行。isPerformingWork = true;// previousPriorityLevel 保存当前的优先级级别const previousPriorityLevel = currentPriorityLevel;try {return workLoop(initialTime);} finally {// 清空当前正在处理的任务,避免内存泄漏。currentTask = null;// 恢复之前保存的优先级级别。currentPriorityLevel = previousPriorityLevel;// 标记任务执行结束,允许下次调度。isPerformingWork = false;}
}
workLoop
workLoop
函数是一个任务调度的核心循环,它负责在给定的初始时间内,持续从任务队列(taskQueue
)中取出任务并执行,直到满足某些条件才停止。同时,它还会处理定时器队列(timerQueue
)中的任务,确保任务能在合适的时间执行。
function workLoop(initialTime: number) {let currentTime = initialTime;advanceTimers(currentTime);// 使用 peek 函数从任务队列中取出第一个任务赋值给 currentTask,没有则为nullcurrentTask = peek(taskQueue);// 进入 while 循环,只要任务队列中有任务,并且调度器没有因为调试模式而暂停,就会继续循环。while (currentTask !== null &&!(enableSchedulerDebugging && isSchedulerPaused)) {// 如果当前任务的过期时间大于当前时间,并且需要向宿主环境让步(例如浏览器需要处理其他事件),则跳出循环。if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {break;}// 取出当前任务的回调函数 callback。const callback = currentTask.callback;if (typeof callback === 'function') {// 将当前任务的回调函数置为 null,避免重复执行。currentTask.callback = null;// 设置当前的优先级为当前任务的优先级。currentPriorityLevel = currentTask.priorityLevel;// 判断当前任务是否已经过期。const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;// 执行回调函数 callback,并传入任务是否过期的标志,得到返回的延续回调函数 continuationCallback。const continuationCallback = callback(didUserCallbackTimeout);currentTime = getCurrentTime();// 处理延续回调函数if (typeof continuationCallback === 'function') {// 如果延续回调函数是一个函数,则将其赋值给当前任务的回调函数,再次推进定时器,然后返回 true 表示还有额外的工作。currentTask.callback = continuationCallback;advanceTimers(currentTime);return true;} else {// 如果延续回调函数不是一个函数,并且当前任务是任务队列的第一个任务,则从任务队列中移除该任务,再次推进定时器。if (currentTask === peek(taskQueue)) {pop(taskQueue);}advanceTimers(currentTime);}} else {// 移除该任务pop(taskQueue);}// 取出下一个任务currentTask = peek(taskQueue);}// 判断是否还有任务if (currentTask !== null) {return true;} else {// 如果任务队列中没有任务,检查定时器队列中是否有任务。如果有,使用 requestHostTimeout 函数在合适的时间调用 handleTimeout 函数来处理定时器任务,然后返回 false 表示没有额外的工作。const firstTimer = peek(timerQueue);if (firstTimer !== null) {requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);}return false;}
}
// Tasks are stored on a min heap
var taskQueue: Array<Task> = [];
var timerQueue: Array<Task> = [];
//opaque type 是 Flow 特有的不透明类型声明。不透明类型意味着外部只能知道它的名字,而不能直接访问其内部结构
export opaque type Task = {id: number,callback: Callback | null,priorityLevel: PriorityLevel,startTime: number,expirationTime: number,sortIndex: number,isQueued?: boolean,
};
id: number
:每个任务都有一个唯一的id
,类型为number
,用于标识任务。callback: Callback | null
:callback
是任务要执行的回调函数,类型为Callback
或者null
。Callback
应该是在其他地方定义的一种函数类型。priorityLevel: PriorityLevel
:priorityLevel
表示任务的优先级,PriorityLevel
应该是一个自定义的类型,用于表示不同的优先级等级。startTime: number
:startTime
是任务的开始时间,类型为number
,通常可能是一个时间戳。expirationTime: number
:expirationTime
是任务的过期时间,类型为number
,同样可能是一个时间戳。当任务超过这个时间还未执行,可能会有相应的处理逻辑。sortIndex: number
:sortIndex
是用于排序的索引,类型为number
。在任务调度时,可能会根据这个索引对任务进行排序。isQueued?: boolean
:isQueued
是一个可选属性,类型为boolean
,用于表示任务是否已经被加入到任务队列中。
工具函数之 performWorkUntilDeadline
performWorkUntilDeadline
函数的主要作用是在浏览器的消息循环中持续执行工作任务,直到没有更多的工作或者达到某个截止时间。它会在当前浏览器任务中不断尝试刷新工作,若还有剩余工作则安排下一次消息事件继续执行,若没有剩余工作则停止消息循环。
const performWorkUntilDeadline = () => {// isMessageLoopRunning 是一个布尔标志,用于判断消息循环是否正在运行。只有当消息循环正在运行时,才会执行后续的工作。if (isMessageLoopRunning) {const currentTime = getCurrentTime();// Keep track of the start time so we can measure how long the main thread// has been blocked.startTime = currentTime;
// hasMoreWork 初始化为 true,用于标记是否还有更多的工作需要执行。let hasMoreWork = true;try {// 调用 flushWork(currentTime) 函数来尝试刷新工作。flushWork 函数会根据当前时间处理调度任务,并返回一个布尔值,表示是否还有剩余的工作。hasMoreWork = flushWork(currentTime);} finally {if (hasMoreWork) {// 如果 hasMoreWork 为 true,说明还有工作未完成,调用 schedulePerformWorkUntilDeadline() 函数安排下一次消息事件,以便继续执行剩余的工作。schedulePerformWorkUntilDeadline();} else {// 如果 hasMoreWork 为 false,说明所有工作都已完成,将 isMessageLoopRunning 标志设置为 false,表示消息循环停止运行。isMessageLoopRunning = false;}}}
};
const getCurrentTime =// $FlowFixMe[method-unbinding]typeof performance === 'object' && typeof performance.now === 'function'? () => performance.now(): () => Date.now();
performance.now():基准点是页面的导航开始时间(即页面加载时刻),返回的是当前时间距离页面加载的时间差。它的值通常较小,且不容易受到系统时钟调整的影响。
Date.now():基准点是1970年1月1日00:00:00 UTC(Unix纪元),返回的是自该时刻以来的毫秒数。它的值是一个非常大的数字,可能会受到系统时间变更的影响。
工具函数之 schedulePerformWorkUntilDeadline
实现 schedulePerformWorkUntilDeadline
函数,该函数的作用是安排 performWorkUntilDeadline
函数尽快执行。代码会根据当前环境支持的特性,选择不同的异步调度方式来实现这一功能,确保在不同环境下都能高效地调度任务。
使用优先顺序: setImmediate > MessageChannel > setTimeout
let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {schedulePerformWorkUntilDeadline = () => {localSetImmediate(performWorkUntilDeadline);};
} else if (typeof MessageChannel !== 'undefined') {const channel = new MessageChannel();const port = channel.port2;channel.port1.onmessage = performWorkUntilDeadline;schedulePerformWorkUntilDeadline = () => {port.postMessage(null);};
} else {// We should only fallback here in non-browser environments.schedulePerformWorkUntilDeadline = () => {// $FlowFixMe[not-a-function] nullable valuelocalSetTimeout(performWorkUntilDeadline, 0);};
}
const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null;const localClearTimeout =typeof clearTimeout === 'function' ? clearTimeout : null;const localSetImmediate =typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
setImmediate 不再推荐使用。用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数。
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setImmediate
https://developer.mozilla.org/zh-CN/docs/Web/API/MessageChannel
例子:关于MessageChannel的使用
// 创建一个新的消息通道
const channel = new MessageChannel();// port1 和 port2 是两个相互连接的端口
const port1 = channel.port1;
const port2 = channel.port2;// 在 port1 上设置消息监听
port1.onmessage = (event) => {console.log('port1 收到消息:', event.data);
};// 通过 port2 发送消息
port2.postMessage('Hello from port2!');// 也可以在 port2 上设置监听
port2.onmessage = (event) => {console.log('port2 收到消息:', event.data);
};// 通过 port1 发送消息
port1.postMessage('Hello from port1!');
例子:与iframe的通信
const iframe = document.querySelector('iframe');
const channel = new MessageChannel();// 将 port2 传递给 iframe
iframe.contentWindow.postMessage('init', '*', [channel.port2]);// 在主窗口使用 port1
channel.port1.onmessage = (event) => {console.log('来自 iframe 的消息:', event.data);
};
工具函数之 peek
取出堆顶元素,没有则返回null。
function peek<T: Node>(heap: Heap<T>): T | null {return heap.length === 0 ? null : heap[0];
}
type Heap<T: Node> = Array<T>;
type Node = {id: number,sortIndex: number,...
};
工具函数之 push
将一个node参数加入到数组中,并重新排序。
function push<T: Node>(heap: Heap<T>, node: T): void {const index = heap.length;heap.push(node);// 重新调整堆siftUp(heap, node, index);
}
工具函数之 pop
pop
函数用于从最小堆(小顶堆)中移除并返回堆顶元素(即堆中最小的元素),同时维护堆的性质。小顶堆是一种特殊的完全二叉树,其中每个节点的值都小于或等于其子节点的值。该函数会先检查堆是否为空,如果为空则返回 null
,否则移除堆顶元素,并将堆的最后一个元素移动到堆顶,然后通过 siftDown
操作重新调整堆,使其恢复最小堆的性质。
function pop<T: Node>(heap: Heap<T>): T | null {if (heap.length === 0) {return null;}// 取出第一个元素const first = heap[0];// 删除并返回数组的最后一个元素,改变数组长度const last = heap.pop();// 如何删除的 元素不是第一个元素,便将最后一个元素设置为第一个元素if (last !== first) {heap[0] = last;// 重新调整堆siftDown(heap, last, 0);}return first;
}
工具函数之 siftUp
实现了最小堆(小顶堆)中的 siftUp
操作。最小堆是一种特殊的完全二叉树,树中每个节点的值都小于或等于其子节点的值。siftUp
操作主要用于在向堆中插入新元素或者更新某个元素的值后,通过不断将该元素与其父节点比较并交换位置,使堆重新满足最小堆的性质。
function siftUp<T: Node>(heap: Heap<T>, node: T, i: number): void {let index = i;while (index > 0) {// 无符号右移const parentIndex = (index - 1) >>> 1;const parent = heap[parentIndex];if (compare(parent, node) > 0) {// The parent is larger. Swap positions.heap[parentIndex] = node;heap[index] = parent;index = parentIndex;} else {// The parent is smaller. Exit.return;}}
}
siftDown
函数实现了最小堆(小顶堆)的下沉操作。在最小堆这种数据结构中,每个节点的值都小于或等于其子节点的值。当堆中的某个节点的值发生变化(通常是增大),或者新插入一个节点到堆的底部后需要将其调整到合适位置时,就需要使用下沉操作来维护堆的性质。该函数接收一个堆数组 heap
、一个节点 node
以及该节点在堆中的初始索引 i
,通过不断比较节点与其子节点的大小,并进行交换,将节点下沉到合适的位置。
function siftDown<T: Node>(heap: Heap<T>, node: T, i: number): void {let index = i;const length = heap.length;// 无符号右移const halfLength = length >>> 1;while (index < halfLength) {const leftIndex = (index + 1) * 2 - 1;const left = heap[leftIndex];const rightIndex = leftIndex + 1;const right = heap[rightIndex];// If the left or right node is smaller, swap with the smaller of those.if (compare(left, node) < 0) {if (rightIndex < length && compare(right, left) < 0) {heap[index] = right;heap[rightIndex] = node;index = rightIndex;} else {heap[index] = left;heap[leftIndex] = node;index = leftIndex;}} else if (rightIndex < length && compare(right, node) < 0) {heap[index] = right;heap[rightIndex] = node;index = rightIndex;} else {// Neither child is smaller. Exit.return;}}
}
工具函数之 compare
function compare(a: Node, b: Node) {// Compare sort index first, then task id.const diff = a.sortIndex - b.sortIndex;return diff !== 0 ? diff : a.id - b.id;
}
工具函数之 advanceTimers
advanceTimers
函数的主要功能是根据当前时间 currentTime
对定时器队列 timerQueue
进行处理。它会遍历定时器队列中的任务,根据任务的状态(回调函数是否为空、是否到达开始时间)来决定是移除任务、将任务从定时器队列转移到任务队列 taskQueue
中,还是停止处理。
function advanceTimers(currentTime: number) {// 取出定时队列中的第一个任务let timer = peek(timerQueue);//遍历定时器队列while (timer !== null) {if (timer.callback === null) {// 移除第一个任务pop(timerQueue);} else if (timer.startTime <= currentTime) {// 过期了 移除任务pop(timerQueue);timer.sortIndex = timer.expirationTime;// 添加任务push(taskQueue, timer);} else {// Remaining timers are pending.return;}// 取出任务timer = peek(timerQueue);}
}
工具函数之 requestHostTimeout
function requestHostTimeout(callback: (currentTime: number) => void,ms: number,
) {// 延时执行setTimeouttaskTimeoutID = localSetTimeout(() => {callback(getCurrentTime());}, ms);
}
processRootScheduleInMicrotask函数后面的逻辑,后续再补充。
相关文章:
React19源码系列之 root.render过程
在创建react项目的时候,入口文件总是有这样一行代码 root.render(<App />) 所以 root.render() 执行是怎样的? 下面就来看看。 之前的文章就提及,root是一个 ReactDOMRoot 对象,其原型链上有 render 和 unmount 方法。 ReactDOMHy…...
0804标星_复制_删除-网络ajax请求2-react-仿低代码平台项目
文章目录 1 标星2 复制3 假删除4 恢复5 彻底删除结语 1 标星 操作:标星和取消标星,有2种状态的布尔值。通过更新问卷功能实现。 后端quetion.js添加接口 {// 更新问卷信息url: /api/question/:id,method: patch,response() {return {errno: 0}}},前端q…...
二叉树遍历(C语言版)
前序遍历创建树,中序遍历把创建出来的二叉树的结点打印出来 题目链接:牛客网-二叉树遍历 前序遍历创建树的思想: 把每个结点看作是子树的根节点,以根左右的顺序创建一整棵二叉树 1.空 返回空 2.非空 先是malloc一个结点ÿ…...
OceanBase数据库-学习笔记2-C#/C++程序如何访问
MySQL模式下,程序(C#)连接数据库操作demo SqlSugar public class MainModel {private static readonly ConnectionConfig connectionConfig new ConnectionConfig(){ConnectionString "serverxxx.xxx.xxx.xxx;port2881;user idroot;…...
Python中的Walrus运算符分析
Python中的Walrus运算符(:)是Python 3.8引入的一个新特性,允许在表达式中同时赋值和返回值。它的核心作用是减少重复计算,提升代码简洁性。以下是其适用的典型场景及示例: 1. 在循环中避免重复计算 当循环条件需要多次…...
【深度好文】4、Milvus 存储设计深度解析
引言 作为一款主流的云原生向量数据库,Milvus 通过其独特的存储架构设计来保证高效的查询性能。本文将深入剖析 Milvus 的核心存储机制,特别是其最小存储单元 Segment 的完整生命周期,包括数据写入、持久化、合并以及索引构建等关键环节。 …...
航顺 芯片 开发记录 (一) 2025年4月27日19:23:32
芯片型号: HK32F030MF4P6 第一步:创建工程目录 inc :头文件目录 MDK-ARM : 工程根目录 (新建工程选择该目录) src :相关资源存放位置 官方函数库相关内容 官方函数库大致结构图 ├─HK32F030MLib ├─CMSIS │ ├─CM0 │ │ └─Core │ │ arm_common_table…...
Java 设计模式
Java后端常用设计模式总览表 模式核心思想Spring / Spring Boot应用手写实现核心单例模式 (Singleton)一个类只有一个实例,提供全局访问点Spring容器中的默认Bean都是单例管理volatile synchronized 双重检查锁定,懒加载单例工厂模式 (Factory)统一管理…...
Milvus如何实现关键词过滤和向量检索的混合检索
Milvus 可以实现关键词过滤和向量检索的混合检索,具体来说,可以结合向量搜索与其他属性字段(如关键词、类别标签等)进行联合查询。这样,在检索时不仅考虑向量的相似度,还能根据特定的关键词或标签等条件对数据进行筛选,从而提高检索的精度和灵活性。 1. 理解混合检索的…...
基于Qt5的蓝牙打印开发实战:从扫描到小票打印的全流程
文章目录 前言一、应用案例演示二、开发环境搭建2.1 硬件准备2.2 软件配置 三、蓝牙通信原理剖析3.1 实现原理3.2 通信流程3.3 流程详解3.4 关键技术点 四、Qt蓝牙核心类深度解析4.1 QBluetoothDeviceDiscoveryAgent4.2 QBluetoothDeviceInfo4.3 QBluetoothSocket 五、功能实现…...
Linux日志处理命令多管道实战应用
全文目录 1 日志处理1.1 实时日志分析1.1.1 nginx日志配置1.1.2 nginx日志示例1.1.3 日志分析示例 1.2 多文件合并分析1.3 时间范围日志提取 2 问题追查2.1 进程级问题定位2.2 网络连接排查2.3 硬件故障追踪 3 数据统计3.1 磁盘空间预警3.2 进程资源消耗排名3.3 HTTP状态码统计…...
Node.js CSRF 保护指南:示例及启用方法
解释 CSRF 跨站请求伪造 (CSRF/XSRF) 是一种利用用户权限劫持会话的攻击。这种攻击策略允许攻击者通过诱骗用户以攻击者的名义提交恶意请求,从而绕过我们的安全措施。 CSRF 攻击之所以可能发生,是因为两个原因。首先,CSRF 攻击利用了用户无法辨别看似合法的 HTML 元素是否…...
线性代数—向量与矩阵的范数(Norm)
参考链接: 范数(Norm)——定义、原理、分类、作用与应用 - 知乎 带你秒懂向量与矩阵的范数(Norm)_矩阵norm-CSDN博客 什么是范数(norm)?以及L1,L2范数的简单介绍_l1 norm-CSDN博客 范数(Norm…...
微服务基础-Ribbon
1. Ribbon简介: 客户端的负载均衡: 2....
移除生产环境所有console.log
大多数团队都会要求不能在生产环境输出业务侧的内容,但是往往业务开发人员会有疏漏,所以需要在工程化环境中,整体来管理console.log。我最近也是接到这样一个需求,整理了一下实现方案。 不同团队,不同场景,…...
数字人接大模型第二步:实时语音同步
接上例第一步,还是dh_live项目,增加了一个完整的实时对话样例,包含vad-asr-llm-tts-数字人全流程,以弥补之前的只有固定的问答的不足。 VAD(Voice Activity Detection,语音活动检测)VAD用于检测用户是否正在说话,从而触发后续的语音处理流程。 ASR(Automatic Speech R…...
Tomcat的安装与配置
Tomcat Tomcat是一个Java圈子中广泛使用的HTTP服务器. 后续学习Severlet内容,就是依赖Tomcat. Java程序员,要想写个网站出来,绕不开Tomcat. 我们这里使用Tomcat8 在bin目录下,这两个文件尤为重要,需要说明的是,Tomcat是那Java写的,所以在运行时需要jdk. bat后缀:是Window…...
Spring AI Alibaba - MCP连接 MySQL
先看效果 直接问他数据库有什么表。 大模型调用MySQL进行查询 搭建项目 添加依赖 创建项目后新添加Maven 依赖: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> …...
Spring Cloud Stream喂饭级教程【搜集全网资料整理】
文章较长,建议收藏关注,随时查看 Spring Cloud Stream 简介 Spring Cloud Stream 是 Spring 提供的一个框架,用于构建与共享消息系统相连接的高度可伸缩的事件驱动微服务,它建立在 Spring 已有的成熟组件和最佳实践之上ÿ…...
prometheus手动添加k8s集群外的node-exporter监控
1、部署node-exporter 1)helm方式部署 rootiZj6c72dzbei17o2cuksmeZ:~# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts "prometheus-community" has been added to your repositories rootiZj6c72dzbei17o2cu…...
Linux(Centos版本)中安装Docker
文章目录 Linux(Centos版本)中安装Docker整体流程 Linux(Centos版本)中安装Docker整体流程 进入root权限进行安装: 下面开始安装Docker: 1、安装docker的yum管理工具:记得将yum仓库更改为国内的镜像源&…...
C语言-- 深入理解指针(4)
C语言-- 深入理解指针(4) 一、回调函数二、冒泡排序三、qsort函数3.1 使用qsort函数排序整型数据3.2 使用qsort函数排序double数据3.3 使用qsort来排序结构体数据 四、模仿qsort库函数实现通用的冒泡排序4.1 通用冒泡排序函数排序整型数据4.2 通用冒泡排…...
牟乃夏《ArcGIS Engine地理信息系统开发教程》学习笔记3-地图基本操作与实战案例
目录 一、开发环境与框架搭建 二、地图数据加载与文档管理 1. 加载地图文档(MXD) 2. 动态添加数据源 三、地图浏览与交互操作 1. 基础导航功能 2. 书签管理 3. 量测功能 四、要素选择与属性查询 1. 属性查询 2. 空间查询 五、视图同步与鹰眼…...
Spark Streaming实时数据处理实战:从DStream基础到自定义数据源集成
park-Streaming概述 Spark-Streaming是什么 Spark Streaming 用于流式数据的处理。Spark Streaming 支持的数据输入源很多,例如:Kafka、Flume、Twitter等,以及和简单的 TCP 套接字等等。数据输入后可以用 Spark 的高度抽象原语如:…...
微软GraphRAG的安装和在RAG中的使用体会
文章目录 0. 简介(1)**技术原理**(2)**优势**(3)**开源与演进** 1. 下载graphrag.git2.安装 poetry3.初始化项目:建立cases目录4. 修改.env5.修改settings.yaml,将两处 api_base改成中转站地址:…...
Python学习记录7——集合set()的使用指南
文章目录 引言一、集合特性二、创建方式三、元素操作1、添加元素(1)add(element)(2)update(iterables) 2、删除元素(1)remove(element)(2)discard(element)(3)…...
apkpure 谷歌插件 下载的apk包
谷歌插件市场搜索 apkpure 然后直接搜索下载就行了 想看apk包中的静态资源,直接改apk 为zip后缀解压就行了 apple的ipa包也是相同的道理...
Android四大核心组件
目录 一、为什么需要四大组件? 二、Activity:看得见的界面 核心功能 生命周期图解 代码示例 三、Service:看不见的劳动者 两大类型 生命周期对比 注意陷阱 四、BroadcastReceiver:消息传递专员 两种注册方式 广播类型 …...
WSL2里手动安装Docker 遇坑
在 WSL2 里手动安装 Docker Engine 时遇坑:systemctl 和 service 命令在默认的 WSL2 Ubuntu 中 无法使用,因为 WSL2 没有 systemd。怎么办? 自己操作让 Docker Engine(dockerd)直接跑起来,挂到 /var/run/do…...
【ROS2】ROS开发环境配置——vscode和git
古月21讲-ROS2/1.系统架构/1.5_ROS2开发环境配置/ ROS机器人开发肯定离不开代码编写,课程中会给大家提供大量示例源码,这些代码如何查看、编写、编译 安Linux中安装装git sudo apt install git下载教程源码 《ROS2入门21讲》课程源码的下载方式&#x…...
django.db.models.query_utils.DeferredAttribute object
在 Django 中,当你看到 django.db.models.query_utils.DeferredAttribute 对象时,通常是因为你在查询时使用了 only() 或 defer() 方法来延迟加载某些字段。这两个方法允许你控制数据库查询中的字段加载方式,从而优化查询性能。 only() 方法…...
Linux内核中的编译时安全防护:以网络协议栈控制块校验为例
引言:内存安全的无声守卫者 在操作系统内核开发中,内存溢出引发的错误往往具有极高的隐蔽性和破坏性。Linux内核作为承载全球数十亿设备的基石,其网络协议栈的设计尤其注重内存安全性。本文通过分析一段看似简单的内核代码,揭示Linux如何通过编译时静态检查(Compile-Time…...
第11章 安全网络架构和组件(一)
11.1 OSI 模型 协议可通过网络在计算机之间进行通信。 协议是一组规则和限制,用于定义数据如何通过网络介质(如双绞线、无线传输等)进行传输。 国际标准化组织(ISO)在20世纪70年代晚期开发了开放系统互连(OSI)参考模型。 11.1.1 OSI模型的…...
Git常用命令简明教程
本教程整合并优化了Git核心命令,涵盖初始化、配置、文件操作、分支管理、远程仓库操作及常见场景,适合快速入门和日常参考。命令按使用流程分组,简洁明了,包含注意事项和最佳实践。 1. 初始化与配置 初始化Git仓库并设置基本配置…...
在 Ubuntu 24.04 系统上安装和管理 Nginx
1、安装Nginx 在Ubuntu 24.04系统上安装Nginx,可以按照下面的步骤进行: 1.1、 更新系统软件包列表 在安装新软件之前,需要先更新系统的软件包列表,确保获取到最新的软件包信息。打开终端,执行以下命令: …...
数据结构——二叉树和堆(万字,最详细)
目录 1.树 1.1 树的概念与结构 1.2 树相关的术语 1.3 树的表示法 2.二叉树 2.1 概念与结构 2.2 特殊的二叉树 2.2.1 满二叉树 2.2.2 完全二叉树 2.3 二叉树存储结构 2.3.1 顺序结构 2.3.2 实现顺序结构二叉树 2.3.2.1 堆的概念与结构 2.3.2. 2 堆的插入与删除数据…...
IdeaVim 配置与使用指南
一、什么是 IdeaVim? IdeaVim 是 JetBrains 系列 IDE(如 IntelliJ IDEA, WebStorm, PyCharm 等)中的一个插件,让你在 IDE 里使用 Vim 的按键习惯,大大提升效率。 安装方法: 在 IDE 中打开 设置(Settings) →…...
前端浏览器窗口交互完全指南:从基础操作到高级控制
浏览器窗口交互是前端开发中构建复杂Web应用的核心能力,本文深入探讨23种关键交互技术,涵盖从传统API到最新的W3C提案,助您掌握跨窗口、跨标签页的完整控制方案。 一、基础窗口操作体系 1.1 窗口创建与控制 // 新窗口创建(现代浏…...
考研系列-计算机组成原理第五章、中央处理器
一、CPU的功能及结构 1.运算器的基本结构 2.控制器结构...
python+flask+flask-sockerio,部署后sockerio通信异常
前言 用python开发了一个flask web服务,前端用html,前后端通过socketio通信,开发环境,windowsminicondavscode,开发完成后本地运行没有问题,然后就开始部署,噩梦就开始了。 问题描述 程序是部…...
深度解析:TextRenderManager——Cocos Creator艺术字体渲染核心类
一、类概述 TextRenderManager 是 Cocos Creator 中实现动态艺术字体渲染的核心单例类。它通过整合资源加载、缓存管理、异步队列和自动布局等功能,支持普通字符模式和图集模式两种渲染方案,适用于游戏中的动态文本(如聊天内容、排行榜&…...
同样开源的自动化工作流工具n8n和Dify对比
n8n和Dify作为两大主流工具,分别专注于通用自动化和AI应用开发领域,选择哪个更“好用”需结合具体需求、团队能力及业务场景综合判断。以下是核心维度的对比分析: 一、核心定位与适用场景 维度n8nDify核心定位开源全场景自动化工具ÿ…...
设计模式每日硬核训练 Day 16:责任链模式(Chain of Responsibility Pattern)完整讲解与实战应用
🔄 回顾 Day 15:享元模式小结 在 Day 15 中,我们学习了享元模式(Flyweight Pattern): 通过共享对象,分离内部状态与外部状态,大量减少内存开销。适用于字符渲染、游戏场景、图标缓…...
基于边缘人工智能的AI无人机-更高效更安全的飞行任务执行
基于边缘人工智能的AI无人机-更高效更安全的飞行任务执行 人工智能有可能改变人们的生活和工作方式。人工智能和无人机是近年来发展迅速的两项技术。当这两种技术结合在一起时,它们会创造出许多以前不可能的应用。基于人工智能的无人机旨在独立执行任务,…...
30、不是说字符串是不可变的吗,string s=“abc“;s=“123“不就是变了吗?
一、核心概念澄清:不可变性的真实含义 1、不可变性的定义 字符串不可变性指对象内容不可修改,而非变量不可修改。 类比: 不可变字符串 装在密封信封里的信纸(内容不可更改)变量赋值 更换信封的指向(从…...
线上查询车辆出险记录:快速掌握事故情况!
在如今汽车成为人们日常不可或缺的交通工具之际,车辆出险记录成为了许多车主关注的焦点之一。为了帮助车主们快速了解车辆出险、理赔、事故记录,现在有了一种便捷的方式,那就是通过API接口在线查询。本文将介绍如何利用API接口,通…...
Python爬虫课程实验指导书
1.1Requests类库的认知 1.1.1 认识请求类库 Requests是用Python语言编写,基于,采用Apache2 Licensed开源协议的。它比urllib更加方便,可以节约我们大量的工作,完全满足HTTP测试需求。urllibHTTP库 Requests官网地址:ht…...
streamlit实现非原生的按钮触发效果 + flask实现带信息的按钮触发
目录 简介不携带信息的触发隐藏指定st.button(label, key)触发button的html代码汇总 携带信息的触发为什么需要携带信息前端JavaScript修改flask处理总代码 简介 由于streamlit可以同时在实现前后端结合,非常方便,但是这也造成了user难以方便的对页面的…...
机器学习基础——Seaborn使用
1.使用tips数据集,创建一个展示不同时间段(午餐/晚餐)账单总额分布的箱线图 import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as snstips pd.read_csv(./tips.csv)sns.boxplot(data tips,x time,y total_bill, )plt.show() 2.使用…...
Godot开发2D冒险游戏——第三节:游戏地图绘制
一、初步构建游戏地图 在游戏场景当中添加一个新的子节点:TileMapLayer 这一层称为瓦片地图层 根据提示,下一步显然是添加资源 为TileMapLayer节点添加一个TileSet 将地板添加进来,然后选择自动分割图集 自定义时要确保大小合适 让Godot自…...