位掩码概念及实践
最近在阅读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;
}