Skip to content

React 中的位运算及其应用

为什么要用位运算?

位运算就是直接对整数在内存中的二进制位进行操作。

比如:

  • 0 在二进制中用 0 表示,我们用 0000 代表;
  • 1 在二进制中用 1 表示,我们用 0001 代表;

如果一个值即代表 A 又代表 B 那么就可以通过位运算的 | 来处理。

js
const A = 0b0000000000000000000000000000001;
const B = 0b0000000000000000000000000000010;
const C = 0b0000000000000000000000000000100;
const N = 0b0000000000000000000000000000000;
const value = A | B;
console.log((value & A) !== N); // true
console.log((value & B) !== N); // true
console.log((value & C) !== N); // false

位掩码: 对于常量的声明(如上的 A B C )必须满足只有一个 1 位,而且每一个常量二进制 1 的所在位数都不同,如下所示:

0b0000000000000000000000000000001 = 1
0b0000000000000000000000000000010 = 2
0b0000000000000000000000000000100 = 4
0b0000000000000000000000000001000 = 8
0b0000000000000000000000000010000 = 16
0b0000000000000000000000000100000 = 32
0b0000000000000000000000001000000 = 64
...

二进制 如果都是 2 的幂数,那么可以用这些变量来删除, 比较,合并这些常量。

React 位掩码场景(1)—更新优先级

更新优先级

React 解决方案就是多个更新优先级的任务存在的时候,高优先级的任务会优先执行,等到执行完高优先级的任务,在回过头来执行低优先级的任务,这样保证了良好的用户体验。

在新版本 React 中,每一个更新中会把待更新的 fiber 增加了一个更新优先级,我们这里称之为 lane ,而且存在不同的更新优先级,这里枚举了一些优先级,如下所示:

js
export const NoLanes = /*                        */ 0b0000000000000000000000000000000;
const SyncLane = /*                        */ 0b0000000000000000000000000000001;

const InputContinuousHydrationLane = /*    */ 0b0000000000000000000000000000010;
const InputContinuousLane = /*             */ 0b0000000000000000000000000000100;

const DefaultHydrationLane = /*            */ 0b0000000000000000000000000001000;
const DefaultLane = /*                     */ 0b0000000000000000000000000010000;

const TransitionHydrationLane = /*                */ 0b0000000000000000000000000100000;
const TransitionLane = /*                        */ 0b0000000000000000000000001000000;

lane 的代表的数值越小,此次更新的优先级就越大。 还有一个问题,React 在 render 阶段可能被中断,在这个期间会产生一个更高优先级的任务,那么会再次更新 lane 属性,这样多个更新就会合并,这样一个 lane 可能需要表现出多个更新优先级。

所以通过位运算,让多个优先级的任务合并,这样可以通过位运算分离出高优先级低优先级的任务。

*** 分离高优先级任务 在 React 底层就是通过 getHighestPriorityLane 分离出高优先级的任务:

js
function getHighestPriorityLane(lanes) {
  return lanes & -lanes;
}

举例:

js
const SyncLane = 0b0000000000000000000000000000001;
const InputContinuousLane = 0b0000000000000000000000000000100;
const lane = SyncLane | InputContinuousLane;
console.log((lane & -lane) === SyncLane); // true

React 位掩码场景(2)——更新上下文

更新上下文状态—ExecutionContext

React 中常用的更新上下文表示:

js
export const NoContext = /*             */ 0b0000000;
const BatchedContext = /*               */ 0b0000001;
const EventContext = /*                 */ 0b0000010;
const DiscreteEventContext = /*         */ 0b0000100;
const LegacyUnbatchedContext = /*       */ 0b0001000;
const RenderContext = /*                */ 0b0010000;
const CommitContext = /*                */ 0b0100000;
export const RetryAfterError = /*       */ 0b1000000;

在 React 事件系统中给 executionContext 赋值 EventContext,在执行完事件后,再重置到之前的状态。

js
function batchedEventUpdates() {
  var prevExecutionContext = executionContext;
  executionContext |= EventContext; // 赋值事件上下文 EventContext
  try {
    return fn(a); // 执行函数
  } finally {
    executionContext = prevExecutionContext; // 重置之前的状态
  }
}

React 位掩码场景 (3) —更新标识 flag

经历了更新优先级 lane 判断是否更新,又通过更新上下文 executionContext 来判断更新的方向,那么到底更新什么? 又有哪些种类的更新呢? image

先来看一下 React 应用中存在什么种类的 flags:

js
export const NoFlags = /*                      */ 0b00000000000000000000000000;
export const PerformedWork = /*                */ 0b00000000000000000000000001;
export const Placement = /*                    */ 0b00000000000000000000000010;
export const Update = /*                       */ 0b00000000000000000000000100;
export const Deletion = /*                     */ 0b00000000000000000000001000;
export const ChildDeletion = /*                */ 0b00000000000000000000010000;
export const ContentReset = /*                 */ 0b00000000000000000000100000;
export const Callback = /*                     */ 0b00000000000000000001000000;
export const DidCapture = /*                   */ 0b00000000000000000010000000;
export const ForceClientRender = /*            */ 0b00000000000000000100000000;
export const Ref = /*                          */ 0b00000000000000001000000000;
export const Snapshot = /*                     */ 0b00000000000000010000000000;
export const Passive = /*                      */ 0b00000000000000100000000000;
export const Hydrating = /*                    */ 0b00000000000001000000000000;
export const Visibility = /*                   */ 0b00000000000010000000000000;
export const StoreConsistency = /*             */ 0b00000000000100000000000000;

React 的更新流程和如上这个游戏如出一撤,也是分了两个阶段:

  • 第一个阶段就像寻宝的小朋友一样,找到待更新的地方,设置更新标志 flags。
  • 接下来在另一个阶段,通过 flags 来证明当前 fiber 发生了什么类型的更新,然后执行这些更新。
js
const NoFlags = 0b00000000000000000000000000;
const PerformedWork = 0b00000000000000000000000001;
const Placement = 0b00000000000000000000000010;
const Update = 0b00000000000000000000000100;
//初始化
let flag = NoFlags;

//发现更新,打更新标志
flag = flag | PerformedWork | Update;

//判断是否有  PerformedWork 种类的更新
if (flag & PerformedWork) {
  //执行
  console.log("执行 PerformedWork");
}

//判断是否有 Update 种类的更新
if (flag & Update) {
  //执行
  console.log("执行 Update");
}

if (flag & Placement) {
  //不执行
  console.log("执行 Placement");
}