跳到主要内容

React中的位运算

位运算

十进制0123456789101112131415
二进制0000000100100011010001010110011110001001101010111100110111101111
八进制012345671011121314151617
十六进制0123456789ABCDEF

基于二进制的位运算能够很方便的表达「增」、「删」、「改」、「查」

例如后台管理系统中的复杂权限控制就可以使用二进制来表示与操作;还有 linux 的文件权限系统

与二进制有关的运算

  • 与(&):有 0 就是 0
1 & 0 = 0
0000 0001
0000 0000
---------
0000 0000
  • 或(|):有 1 就是 1
1 | 0 = 1
0000 0001
0000 0000
---------
0000 0001
  • 非(~):0 1 互换
~1 = -2
0000 0001
---------
1111 1110
  • 异或(^):同为 0,异为 1
1 ^ 0 = 1
0000 0001
0000 0000
---------
0000 0001

所有这些操作除了按位非(~)会将操作数视为32位,其他操作会按JavaScript中的数字类型来进行操作,通常是视为32位的有符号整数来运算。需要注意的是,因为JavaScript中的数字都是以64位浮点数形式存储的,位运算符会将其操作数转换为32位整数,进行位运算后再转换回64位浮点数。

位运算在权限系统中的应用

下载打印查看审核详情删除编辑创建
00000000

如果对应的位置是 0,说明没有对应权限

例如 0000 0001 表示只有创建的权限;0010 0111 说明有增删改查的权限

添加权限

使用或(|)来添加权限,例如给 0000 0001 添加删除和编辑的权限:

0000 0001
0000 0110
---------
0000 0111

删除权限

使用异或(^)来删除权限,例如给 0010 0111 删除创建的权限

0010 0111
0000 0001
---------
0010 0110

判断是否有某权限

使用与(&)来判断是否有某权限,例如判断 0010 0111 是否有删除的权限

0010 0111
0000 0100
---------
0000 0100
// 结果与删除权限一致,说明有删除权限
// 结果为 0,说明没有

React 中的位运算

  • Fiber 的 flags 属性
  • lane 模型
  • 上下文

Fiber 的 flags 属性

使用二进制标记 fiber 操作的 flags

export const NoFlags = /*                      */ 0b0000000000000000000000000000;
export const PerformedWork = /* */ 0b0000000000000000000000000001;
export const Placement = /* */ 0b0000000000000000000000000010;
export const DidCapture = /* */ 0b0000000000000000000010000000;
export const Hydrating = /* */ 0b0000000000000001000000000000;

这些 flags 就是用来标记 fiber 状态的

针对一个 fiber 的操作,可能有增加、删除、修改。当状态变更时,不直接进行操作,而是使用二进制数字给这个 fiber 标记一个 flag,在后续流程中统一对这个 fiber 进行操作

通过位运算,就可以很好地解决一个 fiber 有多个 flag 标记的问题,方便合并(新增)多个状态

// 初始化 flags
const NoFlags = /* */ 0b0000000000000000000000000000;
const PerformedWork = /* */ 0b0000000000000000000000000001;
const Placement = /* */ 0b0000000000000000000000000010;

let flag = NoFlags

// 使用或运算合并(新增)状态
flag = flag | PerformedWork | Placement

// 使用与运算判断是否具有某个状态
if(flag & PerformedWork) {
// 执行 performedWork
}

lane 模型

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

export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010;

export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000;

例如在 React 源码中,从一套 lanes 中分离出优先级最高的 lane

function getHighestPriorityLanes(lanes) {
switch (getHignestPriorityLane(lanes)) {
case SyncLane:
return SyncLane
case InputContinuousLane:
return InputContinuousLane
// ...
}
}
// 从一套 lanes 中分离出优先级最高的 lane(假设 1 越靠近右侧,表示优先级越高)
export function getHighestPriorityLane(lanes) {
return lanes & -lanes
}

// 假设我们现在针对这两个 lane 进行合并
export const SyncLane = 0b0000000000000000000000000000001;
export const InputContinuousLane = 0b0000000000000000000000000000100;

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

// 对结果取负值
-lanes => 0b1111111111111111111111111111011
// 再对本身做与操作
0b1111111111111111111111111111011
0b0000000000000000000000000000101
---------------------------------
0b0000000000000000000000000000001

// 得到的结果就是最高优先级的 lane

上下文

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

// 未处于 React 上下文
export const NoContext = 0b000
// 处于 render 阶段
export const RenderContext = 0b010
// 处于 commit 阶段
export const CommitContext = 0b100

当执行流程到了 Render 阶段,就会切换上下文,切换到 RenderContext

let executionContext = NoContext
executionContext |= RenderContext

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

// 是否处于 RenderContext 中
(executionContext & RenderContext) !== NoContext
// 是否处于 CommitContext 中
(executionContext & CommitContext) !== NoContext

总结

位运算可以很方便地标记某种状态(例如复杂权限控制),且增删改查的操作也比较简洁高效

在 react 内部,像 fiber 的 flags(fiber 状态标记与合并)、优先级(getHighestPriorityLane)、上下文环境等都使用到了位运算