Fiber 树的构建
我们先来看一个简单的 demo:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
class App extends React.Component {
render() {
return (
<div className="container">
<div className="section">
<h1>This is the title.</h1>
<p>This is the first paragraph.</p>
<p>This is the second paragraph.</p>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
首次渲染的调用栈如下图
以 performSyncWorkOnRoot 和 commitRoot 两个方法为界限,可以把 ReactDOM.render 分为三个阶段:
- Init
- Render
- Commit
Init Phase
render
很简单,直接调用 legacyRenderSubtreeIntoContainer。
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
) {
// 省略对 container 的校验逻辑
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
这里需要注意一点,此时的 element 已经不是 render 中传入的 了,而是经过 React.createElement 转换后的一个 ReactElement 对象。
legacyRenderSubtreeIntoContainer
在这里我们可以看到方法取名的重要性,一个好的方法名可以让你一眼就看出这个方法的作用。legacyRenderSubtreeIntoContainer,顾名思义,这是一个遗留的方法,作用是渲染子树并将其挂载到 container 上。再来看一下入参,children 和 container 分别是之前传入 render 方法的 App 元素和 id 为 root 的 DOM 元素,所以可以看出这个方法会根据 App 元素生成对应的 DOM 树,并将其挂在到 root 元素上。
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function,
) {
let root: RootType = (container._reactRootContainer: any);
let fiberRoot;
if (!root) {
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
// 省略对 callback 的处理逻辑
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
// 省略 else 逻辑
}
return getPublicRootInstance(fiberRoot);
}
下面来细看一下这个方法:
- 首次挂载时,会通过 legacyCreateRootFromDOMContainer 方法创建 container.reactRootContainer 对象并赋值给 root。 container 对象现在长这样:
- 初始化 fiberRoot 为 root.internalRoot,类型为 FiberRootNode。fiberRoot 有一个极其重要的 current 属性,类型为 FiberNode,而 FiberNode 为 Fiber 节点的对应的类型。所以说 current 对象是一个 Fiber 节点,不仅如此,它还是我们要构造的 Fiber 树的头节点,我们称它为 rootFiber。到目前为止,我们可以得到下图的指向关系:
- 将 fiberRoot 以及其它参数传入 updateContainer 形成回调函数,将回调函数传入 unbatchedUpdates 并调用。
unbatchedUpdates
主要逻辑就是调用回调函数 fn,也就是之前传入的 updateContainer。
export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
const prevExecutionContext = executionContext;
executionContext &= ~BatchedContext;
executionContext |= LegacyUnbatchedContext;
try {
// fn 为之前传入的 updateContainer
return fn(a);
} finally {
executionContext = prevExecutionContext;
if (executionContext === NoContext) {
resetRenderTimer();
flushSyncCallbackQueue();
}
}
}
updateContainer
updateContainer 方法做的还是一些杂活,我们简单总结一下:
- 计算当前 Fiber 节点的 lane(优先级)。
- 根据 lane(优先级),创建当前 Fiber 节点的 update 对象,并将其入队。
- 调度当前 Fiber 节点(rootFiber)。
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
const current = container.current;
const eventTime = requestEventTime();
// 计算当前节点的 lane(优先级)
const lane = requestUpdateLane(current);
if (enableSchedulingProfiler) {
markRenderScheduled(lane);
}
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
// 根据 lane(优先级)计算当前节点的 update 对象
const update = createUpdate(eventTime, lane);
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
// 将 update 对象入队
enqueueUpdate(current, update);
// 调度当前 Fiber节点(rootFiber)
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}
scheduleUpdateOnFiber
接着会进入 scheduleUpdateOnFiber 方法,根据 lane(优先级)等于 SyncLane,代码最终会执行 performSyncWorkOnRoot 方法。performSyncWorkOnRoot 翻译过来,就是指执行根节点(rootFiber)的同步任务,所以 ReactDOM.render 的首次渲染其实是一个同步的过程。
到这里大家可能会有个疑问,为什么 ReactDOM.render 触发的首次渲染是一个同步的过程呢?不是说在新的 Fiber 架构下,render 阶段是一个可打断的异步过程。
我们先来看看 lane 是怎么计算得到的,相关逻辑在 updateContainer 中的 requestUpdateLane 方法里:
export function requestUpdateLane(fiber: Fiber): Lane {
const mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
return (SyncLane: Lane);
} else if ((mode & ConcurrentMode) === NoMode) {
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
? (SyncLane: Lane)
: (SyncBatchedLane: Lane);
} else if (
!deferRenderPhaseUpdateToNextBatch &&
(executionContext & RenderContext) !== NoContext &&
workInProgressRootRenderLanes !== NoLanes
) {
return pickArbitraryLane(workInProgressRootRenderLanes);
}
// 省略非核心代码
}
可以看出 lane 的计算是由当前 Fiber 节点(rootFiber)的 mode 属性决定的,这里的 mode 属性其实指的就是当前 Fiber 节点的渲染模式,而 rootFiber 的 mode 属性其实最终是由 React 的启动方式决定的。
React 其实有三种启动模式:
- Legacy Mode:
ReactDOM.render(<App />, rootNode)
。这是目前 React App 使用的方式,当前没有删除这个模式的计划,但是这个模式不支持一些新的功能。 - Blocking Mode:
ReactDOM.createBlockingRoot(rootNode).render(<App />)
。目前正在实验中,作为迁移到 concurrent 模式的第一个步骤。 - Concurrent Mode:
ReactDOM.createRoot(rootNode).render(<App />)
。目前正在实验中,在未来稳定之后,将作为 React 的默认启动方式。此模式启用所有新功能。
因此不同的渲染模式在挂载阶段的差异,本质上来说并不是工作流的差异(其工作流涉及 初始化 → render → commit 这 3 个步骤),而是 mode 属性的差异。mode 属性决定着这个工作流是一气呵成(同步)的,还是分片执行(异步)的。
Render Phase
performSyncWorkOnRoot
核心是调用 renderRootSync 方法
renderRootSync
有两个核心方法 prepareFreshStack 和 workLoopSync,下面来逐个分析。
prepareFreshStack
首先调用 prepareFreshStack 方法,prepareFreshStack 中有一个重要的方法 createWorkInProgress。
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// 通过 current 创建 workInProgress
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 使 workInProgress 与 current 通过 alternate 相互指向
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// 省略 else 逻辑
}
// 省略对 workInProgress 属性的处理逻辑
return workInProgress;
}
下面我们来看一下 workInProgress 究竟是什么?workInProgress 是 createFiber 的返回值,接着来看一下 createFiber。
const createFiber = function(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
return new FiberNode(tag, pendingProps, key, mode);
};
可以看出 createFiber 其实就是在创建一个 Fiber 节点。所以说 workInProgress 其实就是一个 Fiber 节点。
从 createWorkInProgress 中,我们还可以看出:
- workInProgress 节点是 current 节点(rootFiber)的一个副本。
- workInProgress 节点与 current 节点(rootFiber)通过 alternate 属性相互指向。
所以到现在为止,我们的 Fiber 树如下:
workLoopSync
接下来调用 workLoopSync 方法,代码很简单,若 workInProgress 不为空,调用 performUnitOfWork 处理 workInProgress 节点。
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
performUnitOfWork
performUnitOfWork 有两个重要的方法 beginWork 和 completeUnitOfWork,在 Fiber 的构建过程中,我们只需重点关注 beginWork 这个方法。
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, subtreeRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, subtreeRenderLanes);
}
resetCurrentDebugFiberInDEV();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
目前我们只能看出,它会对当前的 workInProgress 节点进行处理,至于怎么处理的,当我们解析完 beginWork 方法再来总结 performUnitOfWork 的作用。
beginWork
根据 workInProgress 节点的 tag 进行逻辑分发。tag 属性代表的是当前 Fiber 节点的类型,常见的有下面几种:
- FunctionComponent:函数组件(包括 Hooks)
- ClassComponent:类组件
- HostRoot:Fiber 树根节点
- HostComponent:DOM 元素
- HostText:文本节点
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// 省略非核心(针对树构建)逻辑
switch (workInProgress.tag) {
// 省略部分 case 逻辑
// 函数组件(包括 Hooks)
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
// 类组件
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
// 根节点
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
// DOM 元素
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
// 文本节点
case HostText:
return updateHostText(current, workInProgress);
// 省略部分 case 逻辑
}
// 省略匹配不上的错误处理
}
当前的 workInProgress 节点为 rootFiber,tag 对应为 HostRoot,会调用 updateHostRoot 方法。
rootFiber 的 tag(HostRoot)是什么来的?核心代码如下:
export function createHostRootFiber(tag: RootTag): Fiber {
// 省略非核心代码
return createFiber(HostRoot, null, null, mode);
}
在创建 rootFiber 节点的时候,直接指定了 tag 参数为 HostRoot。
updateHostRoot
updateHostRoot 的主要逻辑如下:
- 调用 reconcileChildren 方法创建 workInProgress.child。
- 返回 workInProgress.child。
function updateHostRoot(current, workInProgress, renderLanes) {
// 省略非核心逻辑
if (root.hydrate && enterHydrationState(workInProgress)) {
// 省略 if 成立的逻辑
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
resetHydrationState();
}
return workInProgress.child;
}
这里有一点需要注意,通过查看源码,你会发现不仅是 updateHostRoot 方法,所以的更新方法最终都会调用下面这个方法:
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
只是针对不同的节点类型,会有一些不同的处理,最终殊途同归。
reconcileChildren
reconcileChildren 根据 current 是否为空进行逻辑分发。
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
此时 current 节点不为空,会走 else 逻辑,调用 reconcileChildFibers 创建 workInProgress.child 对象。
reconcileChildFibers
根据 newChild 的类型进行不同的逻辑处理。
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
// 省略非核心代码
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
// 省略其他 case 逻辑
}
}
// 省略非核心代码
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
}
// 省略非核心代码
}
newChild 很关键,我们先明确一下 newChild 究竟是什么?通过层层向上寻找,你会在 updateHostRoot 方法中发现它其实是最开始传入 render 方法的 App 元素,它在 updateHostRoot 中被叫做 nextChildren,到这里我们可以做出这样的猜想,rootFiber 的下一个是 App 节点,并且 App 节点是由 App 元素生成的,下面来看一下 newChild 的结构:
可以看出 newChild 类型为 object,$$typeof 属性为 REACT_ELEMENT_TYPE,所以会调用:
placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
reconcileSingleElement
下面继续看 reconcileSingleElement 这个方法:
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const key = element.key;
let child = currentFirstChild;
// 省略 child 不存在的处理逻辑
if (element.type === REACT_FRAGMENT_TYPE) {
// 省略 if 成立的处理逻辑
} else {
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}
方法的调用比较深,我们先明确一下入参,returnFiber 为 workInProgress 节点,element 其实就是传入的 newChild,也就是 App 元素,所以这个方法的作用为:
- 调用 createFiberFromElement 方法根据 App 元素创建 App 节点。
- 将新生成的 App 节点的 return 属性指向当前 workInProgress 节点(rootFiber)。此时 Fiber 树如下图:
- 返回 App 节点。
placeSingleChild
接下来调用 placeSingleChild:
function placeSingleChild(newFiber: Fiber): Fiber {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags = Placement;
}
return newFiber;
}
入参为之前创建的 App 节点,它的作用为:
- 当前的 App 节点打上一个 Placement 的 flags,表示新增这个节点。
- 返回 App 节点。
之后 App 节点会被一路返回到的 reconcileChildren 方法:
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
此时 workInProgress 节点的 child 属性会指向 App 节点。此时 Fiber 树为:
beginWork 小结
beginWork 的链路比较长,我们来梳理一下:
- 根据 workInProgress.tag 进行逻辑分发,调用形如 updateHostRoot、updateClassComponent 等更新方法。
- 所有的更新方法最终都会调用 reconcileChildren,reconcileChildren 根据 current 进行简单的逻辑分发。
- 之后会调用 mountChildFibers/reconcileChildFibers 方法,它们的作用是根据 ReactElement 对象生成 Fiber 节点,并打上相应的 flags,表示这个节点是新增,删除还是更新等等。
- 最终返回新创建的 Fiber 节点。
简单来说就是创建新的 Fiber 字节点,并将其挂载到 Fiber 树上,最后返回新创建的子节点。
performUnitOfWork 小结
下面我们来小结一下 performUnitOfWork 这个方法,先来回顾一下 workLoopSync 方法。
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
它会循环执行 performUnitOfWork,而 performUnitOfWork,我们已经知道它会通过 beginWork 创建新的 Fiber 节点。它还有另外一个作用,那就是把 workInProgress 更新为新创建的 Fiber 节点,相关逻辑如下:
// 省略非核心代码
// beginWork 返回新创建的 Fiber 节点并赋值给 next
next = beginWork(current, unitOfWork, subtreeRenderLanes);
// 省略非核心代码
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
// 若 Fiber 节点不为空则将 workInProgress 更新为新创建的 Fiber 节点
workInProgress = next;
}
所以当 performUnitOfWork 执行完,当前的 workInProgress 都存储着下次要处理的 Fiber 节点,为下一次的 workLoopSync 做准备。
performUnitOfWork 作用总结如下:
- 通过调用 beginWork 创建新的 Fiber 节点,并将其挂载到 Fiber 树上
- 将 workInProgress 更新为新创建的 Fiber 节点。
App 节点的处理
rootFiber 节点处理完成之后,对应的 Fiber 树如下:
接下来 performUnitOfWork 会开始处理 App 节点。App 节点的处理过程大致与 rootFiber 节点类似,就是调用 beginWork 创建新的子节点,也就是 className 为 container 的 div 节点,处理完成之后的 Fiber 树如下:
这里有一个很关键的地方需要大家注意。我们先回忆一下对 rootFiber 的处理,针对 rootFiber,我们已经知道在 updateHostRoot 中,它会提取出 nextChildren,也就是最初传入 render 方法的 element。
那针对 App 节点,它是如何获取 nextChildren 的呢?先来看下我们的 App 组件:
class App extends React.Component {
render() {
return (
<div className="container">
<div className="section">
<h1>This is the title.</h1>
<p>This is the first paragraph.</p>
<p>This is the second paragraph.</p>
</div>
</div>
);
}
}
我们的 App 是一个 class,React 首先会实例化会它:
之后会把生成的实例挂在到当前 workInProgress 节点,也就是 App 节点的 stateNode 属性上:
然后在 updateClassComponent 方法中,会先初始化 instance 为 workInProgress.stateNode,之后调用 instance 的 render 方法并赋值给 nextChildren:
此时的 nextChildren 为下面 JSX 经过 React.createElement 转化后的结果:
<div className="container">
<div className="section">
<h1>This is the title.</h1>
<p>This is the first paragraph.</p>
<p>This is the second paragraph.</p>
</div>
</div>
接着来看一下 nextChildren 长啥样:
props.children 存储的是其子节点,它可以是对象也可以是数组。对于 App 节点和第一个 div 节点,它们都只有一个子节点。对于第二个 div 节点,它有三个子节点,分别是 h1、p、p,所以它的 children 为数组。
并且 props 还会保存在新生成的 Fiber 节点的 pendingProps 属性上,相关逻辑如下:
export function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let owner = null;
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes,
);
return fiber;
}
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
// 省略非核心逻辑
const fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.lanes = lanes;
return fiber;
}
第一个 div 节点的处理
App 节点的 nextChildren 是通过构造实例并调用 App 组件内的 render 方法得到的,那对于第一个 div 节点,它的 nextChildren 是如何获取的呢?
针对 div 节点,它的 tag 为 HostComponent,所以在 beginWork 中会调用 updateHostComponent 方法,可以看出 nextChildren 是从当前 workInProgress 节点的 pendingProps 上获取的。
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// 省略非核心逻辑
const nextProps = workInProgress.pendingProps;
// 省略非核心逻辑
let nextChildren = nextProps.children;
// 省略非核心逻辑
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
我们之前说过,在创建新的 Fiber 节点时,我们会把下一个子节点元素保存在 pendingProps 中。当下次调用更新方法(形如 updateHostComponent )时,我们就可以直接从 pendingProps 中获取下一个子元素。
之后的逻辑同上,处理完第一个 div 节点后的 Fiber 树如下图:
第二个 div 节点的处理
我们先看一下第二个 div 节点:
<div className="section">
<h1>This is the title.</h1>
<p>This is the first paragraph.</p>
<p>This is the second paragraph.</p>
</div>
它比较特殊,有三个字节点,对应的 nextChildren 为
下面我们来看看 React 是如何处理多节点的情况,首先我们还是会进入 reconcileChildFibers 这个方法:
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
// 省略非核心代码
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
}
// 省略非核心代码
}
newChild 即是 nextChildren,为数组,会调用 reconcileChildrenArray 这个方法
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
lanes: Lanes,
): Fiber | null {
// 省略非核心逻辑
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
// 省略非核心逻辑
if (oldFiber === null) {
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// 省略非核心逻辑
}
下面来总结一下这个方法:
- 遍历所有的子元素,通过 createChild 方法根据子元素创建子节点,并将每个字元素的 return 属性指向父节点。
- 用 resultingFirstChild 来标识第一个子元素。
- 将子元素用 sibling 相连。
最后我们的 Fiber 树就构建完成了,如下图:
Fiber 树的构建的更多相关文章
- [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析
[WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...
- kd树的构建以及搜索
构建算法 k-d树是一个二叉树,每个节点表示一个空间范围.表1给出的是k-d树每个节点中主要包含的数据结构. 表1 k-d树中每个节点的数据类型 域名 数据类型 描述 Node-data 数据矢量 数 ...
- 哈夫曼树的构建(C语言)
哈夫曼树的构建(C语言) 算法思路: 主要包括两部分算法,一个是在数组中找到权值最小.且无父结点两个结点位置,因为只有无父结点才能继续组成树: 另一个就是根据这两个结点来修改相关结点值. 结构定义 ...
- 页面渲染机制(一、DOM和CSSOM树的构建)
1.HTML的加载 HTML是一个网页的基础,下载完成后解析 2.其他静态资源加载 解析HTML时,发现其中有其他外部资源链接比如CSS.JS.图片等,会立即启用别的线程下载. 但当外部资源是JS时, ...
- WebKit Inside: DOM树的构建
当客户端App主进程创建WKWebView对象时,会创建另外两个子进程:渲染进程与网络进程.主进程WKWebView发起请求时,先将请求转发给渲染进程,渲染进程再转发给网络进程,网络进程请求服务器.如 ...
- HTML文档解析和DOM树的构建
浏览器解析HTML文档生成DOM树的过程,以下是一段HTML代码,以此为例来分析解析HTML文档的原理 <!DOCTYPE html> <html lang="en&quo ...
- [WebKit内核] JavaScriptCore深度解析--基础篇(一)字节码生成及语法树的构建
看到HorkeyChen写的文章<[WebKit] JavaScriptCore解析--基础篇(三)从脚本代码到JIT编译的代码实现>,写的很好,深受启发.想补充一些Horkey没有写到的 ...
- 【JAVA-JDT-AST】Java抽象语法树的构建、遍历及转成dot格式(附Github源码)
Background: 最近为了重现tree-based clone detection的论文:L. Jiang, G. Misherghi, Z. Su, and S. Glondu. Deckar ...
- 通过LINQ表达式树动态构建查询条件
第一种方法: public static class PredicateExtensions { public static Expression<Func<T, bool>> ...
随机推荐
- linux远程下载文件 的两种方法之 ftp命令和scp命令
ftp命令: 服务器有安装ftp Server,另外一台linux可以使用ftp的client程序来进行文件的拷贝读取和下载. 1. 连接ftp服务器 格式:ftp [hostname| ip-ad ...
- Java线程池的工作流程
线程池刚被创建的时候,只是向系统里申请一个用于执行流程队列和管理线程池的线程资源.在调用execute()添加一个任务时,线程池会按照以下流程执行: 1.如果正在运行的线程数少于corePoolSiz ...
- 2Spring对象创建小结
Spring的对象创建 Spring学习笔记 周芋杉2021/5/15 原理:工厂设计模式,通过反射创建对象. Spring工厂分类 非web环境:ClassPathXmlApplicationCon ...
- golang:指针理解总结
指针的定义 指针是一个代表着某个内存地址的值.这个内存地址往往是在内存中存储的另一个变量的值的起始位置. go指针是提供操作数据的基本桥梁.因为go很多调用,往往复制一份对象,例如函数的参数,如果没有 ...
- XRDP freerdp
服务器上 freerdp 桌面上有XRDP
- IIC通信时遇到问题的解决
如果遇到问题,反复查不到 就DEBUG 下单点运行,执行每一个SCK 和SDA的拉高拉低 看看是否能正常的拉高拉低 先解决掉底层的GPIO的控制问题, 有的时候可能数据引脚为特殊功能引脚
- PID基础
经常有人会问到PID的用法,今天小编在这里例举温度控制中的PID部分,希望能够把PID的具体应用说明白. 先说几个名词: 1.直接计算法和增量算法:这里的所谓增量算法就是相对于标准算法的相邻两次运算之 ...
- JRebel插件使用详解(IDEA热部署)(Day_44)
JRebel插件使用详解 简介 JRebel是一套JavaEE开发工具. Jrebel 可快速实现热部署,节省了大量重启时间,提高了个人开发效率. JRebel是一款JAVA虚拟机插件,它使得JAVA ...
- 聊聊 Spring 的 XML Schema 扩展机制的使用方式
前言 在当前Java生态,Spring算的上是最核心的框架,所有的开发组件想要得到大范围更便捷的使用,都要和Spring进行整合,比如我们熟知的Mybatis.Dubbo等,以及内部封装的各类组件包括 ...
- Docker学习(1) 初识
Docker的使用场景 1 使用Docker容器开发,测试,部署服务 2 创建隔离的运行环境 3 搭建测试环境 4 构建多用户的平台及服务(PaaS)基础设施 5 提供软件即服务(SaaS)应用程序 ...