- Published on
Bitwise Operations in React
Bitwise Operations in React
面试题:React 中哪些地方用到了位运算?
Baisc knowledge of Bit Operations
所谓二进制,指的就是以二为底的一种计数方式。
十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
二进制 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
八进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
十六进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
我们经常会使用二进制来进行计算,基于二进制的位运算能够很方便的表达 “增、删、查、改”。
例如一个后台管理系统,一般的话会有针对权限的控制,一般权限的控制就使用的是二进制:
// 各个权限
permissions = {
SYS_SETTING: {
value: 0b10000000,
info: '系统重要设置权限',
},
DATA_ADMIN: {
value: 0b01000000,
info: '数据库管理权限',
},
USER_MANG: {
value: 0b00100000,
info: '用户管理权限',
},
POST_EDIT: {
value: 0b00010000,
info: '文章编辑操作权限',
},
POST_VIEW: {
value: 0b00001000,
info: '文章查看权限',
},
}
再例如,在 Linux
操作系统里面,x
代表可执行权限,w
代表可写权限,r
代表可读权限,对应的权限值分别就是 1、2、4(2 的幂次方)
使用二进制来表示权限,首先速度上面会更快一些,其次在表示多种权限的时候,会更加方便一些。
比如,现在有 3 个权限 A、B、C...
根据不同的权限做不同的事情:
if (value === A) {
// ...
} else if (value === B) {
// ...
}
在上面的代码中,会有一个问题,目前仅仅只是一对一的关系,但是在实际开发中,往往有很多一对多的关系,一个 value 可能会对应好几个值。
那这个时候如果还是采用这种方式判断,会让 if
else
代码块异常庞大。
二进制相关的运算规则:
- 与 (&) :只要有一位数为 0,那么最终结果就是 0,也就是说,必须两位都是 1,最终结果才是 1
- 或 (|) : 只要有一位数是 1,那么最终结果就是 1,也就是说必须两个都是 0,最终才是 0
- 非 (~) : 对一个二进制数逐位取反,也就是说 0、1 互换
- 异或 (^) : 如果两个二进制位不相同,那么结果就为 1,相同就为 0
1 & 1 = 1
0000 0001
0000 0001
---------
0000 0001
1 & 0 = 0
0000 0001
0000 0000
---------
0000 0000
1 | 0 = 1
0000 0001
0000 0000
---------
0000 0001
1 ^ 0 = 1
0000 0001
0000 0000
---------
0000 0001
~3
0000 0011
// 逐位取反
1111 1100
// 计算结果最终为 -4(涉及到补码的知识)
接下来来看一下位运算在权限系统里面的实际运用:
下载 | 打印 | 查看 | 审核 | 详细 | 删除 | 编辑 | 创建 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
如果是 0,代表没有权限,如果是 1,代表有权限
0000 0001
代表只有创建的权限,0010 0011
代表有查看、编辑以及创建的权限
Add Permissions
直接使用或运算即可。
0000 0011
目前有创建和编辑的权限,我们要给他添加一个查看的权限 0010 0000
0000 0011
0010 0000
---------
0010 0011
Remove Permissions
可以使用异或
0010 0011
目前有查看、编辑和创建,取消编辑的权限 0000 0010
0010 0011
0000 0010
---------
0010 0001
Check whether have a certain permission
可以使用与来进行判断
0011 1100
(查看、审核、详细、删除) ,判断是否有查看 (0010 0000
) 权限、再判断是否有创建 (0000 0001
) 权限
0011 1100
0010 0000
---------
0010 0000
// 判断是否有“查看”权限,做与操作时得到了“查看”权限值本身,说明有这个权限
0011 1100
0000 0001
---------
0000 0000
// 最终得到的值为 0,说明没有此权限
通过上面的例子,我们会发现使用位运算确确实实非常的方便,接下来我们就来看一下 React 中针对位运算的使用。
Bit operations in React
Fiber
的Flags
Lane
模型- 上下文
Fiber Flags
在 React 中,用来标记 Fiber
操作的 Flags
,使用的就是二进制
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
打上一个 Flags
,接下来在后面的流程中针对有 Flags
的 Fiber
统一进行操作。
通过位运算,就可以很好的解决一个 Fiber
有多个 Flags
标记的问题,方便合并多个状态
// 初始化一些 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')
}
Lane Model
Lane
模型也是一套优先级机制,相比 Scheduler
,Lane
模型能够对任务进行更细粒度的控制。
在 React 中,"lane"模型和"Scheduler"是两个不同的概念,具有不同的作用和功能。
- Lane 模型(Lane Model):Lane 模型是 React Fiber 架构中的一部分,用于跟踪和管理组件更新的优先级。它通过将不同类型的更新任务划分为不同的优先级级别(如 Sync、Batched、Idle 等),从而实现更细粒度的调度和控制。Lane 模型通过位运算的方式表示不同的任务优先级,以便进行高效的状态管理和更新调度。
- Scheduler:Scheduler 是 React 中负责任务调度和协调的模块。它负责根据任务的优先级,将任务分配给适当的执行环境(如浏览器的事件循环),以确保任务在适当的时间执行。Scheduler 利用 Lane 模型中的优先级信息,根据任务的紧急程度和可用资源进行智能调度,以提高应用程序的性能和响应能力。
总结:Lane 模型是 React Fiber 架构中用于管理组件更新优先级的机制,而 Scheduler 是负责任务调度和协调的模块。Lane 模型通过位运算表示不同的任务优先级,Scheduler 利用 Lane 模型的信息进行智能调度,以提高 React 应用程序的性能。
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100
// ...
例如在 React 源码中,有一段如下的代码:
// 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 组合到一起就变成了一个 Lanes 0010 0011
// getHighestPriorityLane 这个方法要做的事情就是分离出优先级最高的
// 0010 0011 ----> getHighestPriorityLane -----> 0000 0001
export function getHighestPriorityLane(lanes) {
return lanes & -lanes
}
假设现在我们针对两个 Lane 进行合并
const SyncLane: Lane = /* */ 0b0000000000000000000000000000001
const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100
合并出来就是一个 Lanes,合并出来的结果如下:
0b0000000000000000000000000000001
0b0000000000000000000000000000100
---------------------------------
0b0000000000000000000000000000101
0b0000000000000000000000000000101
是我们的 Lanes
,接下来取负值
-lanes = 0b1111111111111111111111111111011
对于二进制数
0b0000000000000000000000000000101
(表示十进制数 5),取其负值的过程如下:
首先将该二进制数取反(按位取反),即将 0 变为 1,将 1 变为 0,得到
0b1111111111111111111111111111010
。然后对取反后的二进制数进行加 1 操作。
0b1111111111111111111111111111010 + 1 = 0b1111111111111111111111111111011
最终得到的二进制数为
0b1111111111111111111111111111011
,表示其负值。根据补码表示法,该二进制数对应的十进制值为-5。因此,二进制数
0b0000000000000000000000000000101
的负值为 -5。
最后一步,再和本身的 Lanes
做一个 &
操作:
0b0000000000000000000000000000101
0b1111111111111111111111111111011
---------------------------------
0b0000000000000000000000000000001
经过 &
操作之后,就把优先级最高的 Lane
给分离出来了。
Context
在 React 源码内部,有多个上下文:
// 未处于 React 上下文
export const NoContext = /* */ 0b000
// 处于 batchedUpdates 上下文
const Batche render 阶段
export const RenderContext = /* */ 0b010
// 处于 commit 阶段
export const CommitContext = /* */ 0b100
当执行流程到了 render
阶段,那么接下来就会切换上下文,切换到 RenderContext
let executionContext = NoContext // 一开始初始化为没有上下文
executionContext |= RenderContext
在执行方法的时候,就会有一个判断,判断当前处于哪一个上下文
// 是否处于 RenderContext 上下文中,结果为 true
;((executionContext & RenderContext) !==
NoContext(
// 是否处于 CommitContext 上下文中,结果为 false
executionContext & CommitContext
)) !==
NoContext
如果要离开某一个上下文
// 从当前上下文中移除 RenderContext 上下文
executionContext &= ~RenderContext
// 是否处于 RenderContext 上下文中,结果为 false
;(executionContext & CommitContext) !== NoContext
Answers to quesitons
题目:React 中哪些地方用到了位运算?
参考答案:
位运算可以很方便的表达“增、删、查、改”。在 React 内部,像
Flags
、状态、优先级等操作都大量使用到了位运算。细分下来主要有如下的三个地方:
Fiber
的Flags
Lane
模型- 上下文