React源码中的位运算技巧
大家好,我卡颂。
这两年有不少朋友和我吐槽React
源码,比如:
- 调度器为什么用小顶堆这种数据结构,直接用数组不行?
- 源码里各种单向链表、环状链表,直接用数组不行?
- 源码里各种位运算,有必要么?
React
从不吝惜将源码写的很复杂。文章图片
在涉及
状态
、标记位
、优先级
操作的地方大量使用了位运算。本文会讲解其中比较有代表性的部分。学到之后,当遇到类似场景时露一手,你就是业务线最靓的仔。
文章图片
几个常用位运算 在
JS
中,位运算
的操作数会先转换为Int32
(32位有符号整型),执行完位运算
会Int32
对应浮点数。在
React
中,主要用到3种位运算符 —— 按位与、按位或、按位非。按位与(&)
对于两个二进制操作数的每个
bit
,如果都为1,则结果为1,否则为0。举个例子,计算
3 & 2
,首先将操作数转化为Int32
:// 3对应的 Int32
0b000 0000 0000 0000 0000 0000 0000 0011
// 2对应的 Int32
0b000 0000 0000 0000 0000 0000 0000 0010
为了直观,我们排除前面的0,只保留最后8位(实际参与计算的应该是32位):
0000 0011
& 0000 0010
-----------
0000 0010
所以
3 & 2
计算结果转化为浮点数后为2。按位或(|) 对于两个二进制操作数的每个
bit
,如果都为0,则结果为0,否则为1。计算
10 | 3
:0000 1010
| 0000 0011
-----------
0000 1011
计算结果转化为浮点数后为11。
按位非(~) 对一个二进制操作数的每个
bit
,逐位进行取反操作(0、1互换)对于
~3
,将3转化为Int32
后逐位取反:// 3对应的 Int32
0b000 0000 0000 0000 0000 0000 0000 0011
// 逐位取反
0b111 1111 1111 1111 1111 1111 1111 1100
计算结果转化为浮点数后为-4。
如果你对这个结果有疑惑,可以去了解
补码
相关知识
让我们从易到难,看看位运算在React
中的应用。标记状态
React
源码内部有多个上下文环境,在执行函数时经常需要判断当前处在哪个上下文环境中。假设共有三种上下文情况:
// A上下文
const A = 1;
// B上下文
const B = 2;
// 没有处在上下文
const NoContext = 0;
当进入某个上下文时,可以使用
按位或
操作标记进入:// 当前所处上下文
let curContext = 0;
// 进入A上下文
curContext |= A;
我们用8位二进制举例(同样,实际应该是Int32,这里是为了简化),
curContext
与A
执行按位或
操作:0000 0000// curContext
| 0000 0001// A
-----------
0000 0001
此时可以结合
按位与
操作与NoContext
来判断是否处在某一上下文中:// 是否处在A上下文中 true
(curContext & A) !== NoContext// 是否处在B上下文中 false
(curContext & B) !== NoContext
离开某上下文后,结合
按位与
、按位非
移除标记:// 从当前上下文中移除上下文A
curContext &= ~A;
// 是否处在A上下文中 false
(curContext & A) !== NoContext
curContext
与~A
执行按位与
操作:0000 0001// curContext
& 1111 1110// ~A
-----------
0000 0000
即从
curContext
中移除A
。当业务中需要同时处理多个状态时,可以使用如上位运算技巧。
文章图片
优先级计算 在
React
中,不同情况下调用this.setState
触发的更新会拥有不同优先级。优先级之间的比较、挑选同样使用了位运算。具体来说,
React
中用31个bit
位保存更新(之所以是31而不是32是因为Int32
的最高位是符号位,不保存具体的数)。处在越低
bit
位的更新优先级越高(越需要优先处理)。文章图片
举个例子,假设当前应用存在2个更新:
0b000 0000 0000 0000 0000 0000 0001 0001
其中第1位的更新优先级最高(需要同步处理),第5位为默认优先级。
React
经常需要找出当前最高优先级的更新在哪一位(如上例子中在第一位),方法如下:function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
解释下,由于
Int32
采用补码表示,所以-lanes
可以看作如下两步操作:- lanes取反(~lanes)
- 加1
lanes0001 0001
~lanes 1110 1110 // 第一步
+11110 1111 // 第二步
则
lanes & -lanes
如下:0001 0001 // lanes
& 1110 1111 // -lanes
-----------
0000 0001
取到的就是第一位(已有更新中最高的优先级)。
总结 虽然业务中不常使用位操作,但在特定场景下位操作时很方便、高效的方式。
这波操作你爱了么?
【React源码中的位运算技巧】欢迎加入人类高质量前端框架研究群,带飞
推荐阅读
- 热闹中的孤独
- JS中的各种宽高度定义及其应用
- 我眼中的佛系经纪人
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售
- Android中的AES加密-下
- 放下心中的偶像包袱吧
- C语言字符函数中的isalnum()和iscntrl()你都知道吗
- C语言浮点函数中的modf和fmod详解
- C语言中的时间函数clock()和time()你都了解吗
- 如何在Mac中的文件选择框中打开系统隐藏文件夹