← 返回列表

位掩码概念及实践

最近在阅读React源码时,发现其内部大量使用二进制来组合和判断运行模式。通过使用按位与(&)判断当前模式是否包含目标模式。这类通过“二进制标志位”控制逻辑的方式,即是「位掩码」。

本文对位掩码的概念、优点及实际框架中的使用方式进行总结。

1. 什么是位掩码

位掩码指的是:使用一个整数的不同二进制位表示一组布尔标志,并通过按位操作(位与、位或、位非等)对这些标志进行组合、设置、清除与判断。

它的核心思想是:一个整数可表示多个状态,每个位表示一个布尔值。

2. 位掩码的优点

  • 节省内存:位掩码使用二进制数表示多个标志或选项,例如一个32位整数可以表示32个布尔标志。相比使用单独的变量或数据结构来表示每个标志,可以大大节省内存空间。
  • 高效性:位掩码进行的位运算通常可以在硬件层面上高效执行,因为它们可以利用底层的位运算指令,而无需使用更复杂的算法或数据结构。
  • 简洁性:多个状态组合成一个二进制数,不需要数组或对象来管理。
  • 灵活性:通过简单的按位操作即可实现“合并、判断、移除”等操作。

3. 位掩码常见使用场景

3.1 权限管理

Linux的权限字符串如:drwxr-xr-x 对应为四部分:

  • 文件类型:d
  • 用户权限:rwx
  • 组权限:r-x
  • 其他用户权限:r-x

r/w/x 三个位分别对应:

  • r = 4 (0b100)
  • w = 2 (0b010)
  • x = 1 (0b001)

组合权限只需要将位相加,例如:

r-x = 0b101 = 5
rwx = 0b111 = 7

如果使用布尔值记录权限,则一个权限需要1个字节,三个权限至少3个字节,文件类型等flag也要单独存储。 而使用位掩码,只需几个bit即可表示全部信息,节省大量空间。

3.2 组件状态管理

例如前端组件的是否可见、是否选中、是否禁用、是否处于加载状态,都可以用不同的bit表示,并组合在一个整数中。

4. JavaScript 中的位运算

4.1 按位与(&)

两个操作数对应的二进位都为 1 时,该位的结果值才为 1。衍生:按位与赋值(&=),类似按位加赋值(+=)

var a = 0b1111;
var b = 0b0100;
var c = a & b;
console.log(c.toString(2)); // 100

使用场景:判断变量是否有指定状态(a & b)。

4.2 按位或(|)

在其中一个或两个操作数对应的二进制位为 1 时,该位的结果值为 1。衍生:按位或赋值(|=)

var a = 0b0110;
var b = 0b1110;
var c = a | b;
console.log(c.toString(2)); // 1110

4.3 按位非(~)

按位非运算符(~)将操作数的位反转。

var a = 0b0110;
var b = ~a;
console.log(a); // 6,对应32位有符号整数:0000 .... 0110
console.log(b); // 值为-7, 对应32位有符号整数: 1111 .... 1001
console.log(b.toString(2)); // -7的二进制表示,非存储的32位值:-111

4.4 按位异或(^)

在两个操作数有且仅有一个对应的二进制位为 1 时,该位的结果值为 1。衍生:按位异或赋值(^=)

var a = 0b0110;
var b = 0b0001;
var c = a ^ b;
console.log(c.toString(2)); // 111

4.5 左移(<<)

将第一个操作数向左移动指定位数,左边超出的位数将会被清除,右边将会补零。衍生:左移赋值(<<=)

var a = 0b0010;
var b = a << 2;
console.log(b.toString(2)); // 100

4.6 右移(>>)

将第一个操作数向右移动指定位数,左边移出的空位补符号位。

var a = 0b1000;
var b = a >> 2;
console.log(b.toString(2)); // 10

5. 位掩码在 React 中的应用

5.1 运行模式的判断

React 中定义了多个运行模式,每一位代表一个模式:

export const NoMode = /*                         */ 0b0000000;
export const ConcurrentMode = /*                 */ 0b0000001;
export const ProfileMode = /*                    */ 0b0000010;
export const DebugTracingMode = /*               */ 0b0000100;
export const StrictLegacyMode = /*               */ 0b0001000;
export const StrictEffectsMode = /*              */ 0b0010000;
export const ConcurrentUpdatesByDefaultMode = /* */ 0b0100000;
export const NoStrictPassiveEffectsMode = /*     */ 0b1000000;

判断是否处于某个模式:

if (currentMode & StrictEffectsMode) {
  // 当前启用了 StrictEffectsMode
}

5.2 任务调度

React 的更新优先级由 lanes 表示,每一位表示一个 lane,示例如下:

export const TotalLanes = 31;
export const NoLanes: Lane = /*                         */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;
export const SyncHydrationLane: Lane = /*               */ 0b0000000000000000000000000000001;
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000010;
export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000001000;
export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000010000;
export const DefaultLane: Lane = /*                     */ 0b0000000000000000000000000100000;
const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000001000000;
const TransitionLanes: Lane = /*                        */ 0b0000000001111111111111110000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000001000000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000001000000000000000000000;
const RetryLanes: Lane = /*                             */ 0b0000011110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;
export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;
const NonIdleLanes: Lane = /*                           */ 0b0000111111111111111111111111111;
export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;
export const IdleLane: Lane = /*                        */ 0b0010000000000000000000000000000;
export const OffscreenLane: Lane = /*                   */ 0b0100000000000000000000000000000;
export const DeferredLane: Lane = /*                    */ 0b1000000000000000000000000000000;

通过位运算即可:

  • 合并 lanes
  • 判断是否包含某个优先级
  • 取交集
  • 移除某一类 lane

5.3 相关工具方法

// 合并
export function mergeLanes(a, b): {
  return a | b;
}

// 移除子集
export function removeLanes(set, subset): {
  return set & ~subset;
}

// 交集
export function intersectLanes(a, b): {
  return a & b;
}
// 判断是否有重叠的lane
export function includesSomeLane(a, b) {
  return (a & b) !== NoLanes;
}

// 判断是否为子集
export function isSubsetOfLanes(set, subset) {
  return (set & subset) === subset;
}