Skip to content

React 中的位运算

面试题:React 中哪些地方用到了位运算?

  • fiber 的 flags
  • lane 模型
  • 上下文
  1. fiber 的 flags

    在 React 中,用来标记 fiber 操作的 flags,使用的就是二进制:

    bitwise-operation.js
    js
    export 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 标记的问题,方便合并多个状态

    bitwise-operation.js
    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");
    }
  2. lane 模型

    lane 模型也是一套优先级机制,相比 Scheduler,lane 模型能够对任务进行更细粒度的控制。

    bitwise-operation.js
    js
    export const NoLanes = 0b0000000000000000000000000000000;
    export const NoLane = 0b0000000000000000000000000000000;
    export const SyncLane = 0b0000000000000000000000000000001;
    export const InputContinuousHydrationLane = 0b0000000000000000000000000000010;
    export const InputContinuousLane = 0b0000000000000000000000000000100;

    例如在 React 源码中,有一段如下的代码:

    bitwise-operation.js
    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 进行合并:

    JavaScript
    const SyncLane: Lane = 0b0000000000000000000000000000001;
    const InputContinuousLane: Lane = 0b0000000000000000000000000000100;

    合并出来就是一个 lanes,合并出来的结果如下:

    JavaScript
    0b0000000000000000000000000000001
    0b0000000000000000000000000000100
    ---------------------------------
    0b0000000000000000000000000000101

    0b0000000000000000000000000000101 是我们的 lanes,接下来取负值:

    JavaScript
    -lanes = 0b1111111111111111111111111111011

    最后一步,再和本身的 lanes 做一个 & 操作:

    JavaScript
    0b0000000000000000000000000000101
    0b1111111111111111111111111111011
    ---------------------------------
    0b0000000000000000000000000000001

    经过 & 操作之后,就把优先级最高的 lane 给分离出来了。

  3. 上下文

    在 React 源码内部,有多个上下文:

    JavaScript
    export const NoContext = 0b000;
    const BatchedContext = 0b001;
    export const RenderContext = 0b010;
    export const CommitContext = 0b100;

    当执行流程到了 render 阶段,那么接下来就会切换上下文,切换到 RenderContext:

    JavaScript
    let executionContext = NoContext;
    executionContext |= RenderContext;

    在执行方法的时候,就会有一个判断,判断当前处于哪一个上下文:

    bitwise-operation.js
    js
    // 是否处于 RenderContext 上下文中,结果为 true
    executionContext & (RenderContext !== NoContext);
    // 是否处于 CommitContext 上下文中,结果为 false
    executionContext & (CommitContext !== NoContext);

    如果要离开某一个上下文:

    JavaScript
    // 从当前上下文中移除 RenderContext 上下文
    executionContext &= ~RenderContext;
    // 是否处于 RenderContext 上下文中,结果为 false
    (executionContext & CommitContext) !== NoContext;