React 中的位运算
面试题:React 中哪些地方用到了位运算?
- fiber 的 flags
- lane 模型
- 上下文
fiber 的 flags
在 React 中,用来标记 fiber 操作的 flags,使用的就是二进制:
jsexport const NoFlags = 0b000000000000000000000000000; export const PerformedWork = 0b000000000000000000000000001; export const Placement = 0b000000000000000000000000010; export const DidCapture = 0b000000000000000000010000000; export const Hydrating = 0b000000000000001000000000000;
这些 flags 就是用来标记 fiber 状态的。
之所以要专门抽离 fiber 的状态,是因为这种操作是非常高效的。针对一个 fiber 的操作,可能有增加、删除、修改,但是我不直接进行操作,而是给这个 fiber 打上一个 flag,接下来在后面的流程中针对有 flag 的 fiber 统一进行操作。
通过位运算,就可以很好的解决一个 fiber 有多个 flag 标记的问题,方便合并多个状态
js// 初始化一些 flags const NoFlags = 0b00000000000000000000000000; const PerformedWork = 0b00000000000000000000000001; const Placement = 0b00000000000000000000000010; const Update = 0b00000000000000000000000100; // 一开始将 flag 变量初始化为没有 flag,也就是 NoFlags let flag = NoFlags; // 这里就是在合并多个状态 flag = flag | PerformedWork | Update; // 要判断是否有某一个 flag,直接通过 & 来进行判断即可 //判断是否有 PerformedWork 种类的更新 if (flag & PerformedWork) { //执行 console.log("执行 PerformedWork"); } //判断是否有 Update 种类的更新 if (flag & Update) { //执行 console.log("执行 Update"); } if (flag & Placement) { //不执行 console.log("执行 Placement"); }
lane 模型
lane 模型也是一套优先级机制,相比 Scheduler,lane 模型能够对任务进行更细粒度的控制。
jsexport const NoLanes = 0b0000000000000000000000000000000; export const NoLane = 0b0000000000000000000000000000000; export const SyncLane = 0b0000000000000000000000000000001; export const InputContinuousHydrationLane = 0b0000000000000000000000000000010; export const InputContinuousLane = 0b0000000000000000000000000000100;
例如在 React 源码中,有一段如下的代码:
js// lanes 一套 lane 的组合 function getHighestPriorityLanes(lanes) { // 从 lanes 这一套组合中,分离出优先级最高的 lane switch (getHighestPriorityLane(lanes)) { case SyncLane: return SyncLane; case InputContinuousHydrationLane: return InputContinuousHydrationLane; case InputContinuousLane: return InputContinuousLane; // ... return lanes; } } // lane 在表示优先级的时候,大致是这样的: // 0000 0001 // 0000 0010 // 0010 0000 // lanes 表示一套 lane 的组合 // 比如上面的三个 lane 组合到一起就变成 // 0010 0011 // getHighestPriorityLane 分离出优先级最高的 // 0010 0011 ----> getHighestPriorityLane -----> 0000 0001 export function getHighestPriorityLane(lanes) { return lanes & -lanes; }
假设现在我们针对两个 lane 进行合并:
JavaScriptconst SyncLane: Lane = 0b0000000000000000000000000000001; const InputContinuousLane: Lane = 0b0000000000000000000000000000100;
合并出来就是一个 lanes,合并出来的结果如下:
JavaScript0b0000000000000000000000000000001 0b0000000000000000000000000000100 --------------------------------- 0b0000000000000000000000000000101
0b0000000000000000000000000000101 是我们的 lanes,接下来取负值:
JavaScript-lanes = 0b1111111111111111111111111111011
最后一步,再和本身的 lanes 做一个 & 操作:
JavaScript0b0000000000000000000000000000101 0b1111111111111111111111111111011 --------------------------------- 0b0000000000000000000000000000001
经过 & 操作之后,就把优先级最高的 lane 给分离出来了。
上下文
在 React 源码内部,有多个上下文:
JavaScriptexport const NoContext = 0b000; const BatchedContext = 0b001; export const RenderContext = 0b010; export const CommitContext = 0b100;
当执行流程到了 render 阶段,那么接下来就会切换上下文,切换到 RenderContext:
JavaScriptlet executionContext = NoContext; executionContext |= RenderContext;
在执行方法的时候,就会有一个判断,判断当前处于哪一个上下文:
js// 是否处于 RenderContext 上下文中,结果为 true executionContext & (RenderContext !== NoContext); // 是否处于 CommitContext 上下文中,结果为 false executionContext & (CommitContext !== NoContext);
如果要离开某一个上下文:
JavaScript// 从当前上下文中移除 RenderContext 上下文 executionContext &= ~RenderContext; // 是否处于 RenderContext 上下文中,结果为 false (executionContext & CommitContext) !== NoContext;