几个(不太 in js场景)常用的位运算
在JS
中,位运算一般会转为Int32
(32位有符号整型)。在日常的开发中很少用到位运算,但是
最近在尝试阅读React源码的时候发现其中关于scheduler
调度优先级的运算中,用到了大量的
位运算,遂写一篇博文记录一下。
按位与
概念:将2个二进制操作数进行逐位与操作,如果每位都是1则返回1,否则返回0
示例:
// 3 对应的 Int32
const num01 = 0b000 0000 0000 0000 0000 0000 0000 0011;
// 2 对应的 Int32
const num02 = 0b000 0000 0000 0000 0000 0000 0000 0010;
// result === 0b000 0000 0000 0000 0000 0000 0000 0010;
const result = num01 & num02;
按位或
概念:将2个二进制操作数进行逐位或操作,如果两位中至少有一位是1则返回1,否则返回0
示例:
// 3 对应的 Int32
const num01 = 0b000 0000 0000 0000 0000 0000 0000 0011;
// 2 对应的 Int32
const num02 = 0b000 0000 0000 0000 0000 0000 0000 0010;
// result === 0b000 0000 0000 0000 0000 0000 0000 0011;
const result = num01 & num02;
按位非
概念:对一个二进制操作数进行诸位取反操作,即0与1互换
示例:
// 3 对应的 Int32
const num01 = 0b000 0000 0000 0000 0000 0000 0000 0011;
// result === 0b111 1111 1111 1111 1111 1111 1111 1100;
const result = ~num01;
这里会涉及到补码相关的知识,emmm……已经忘记的差不多了,抽空再补补吧
React源码中的优先级计算
标记状态
在React
中有很多种上下文环境,在执行过程中需要判断当前所处的上下文环境,则可以使用到位运算,示例如下:
const A = 1;
const B = 2;
const noContext = 0;
// 进入上下文可以使用到按位或
let curContext = noContext;
curContext |= A; // curContext === 1
curContext |= B; // curContext === 3;
// 通过与操作判断是否处于某种上下文
curContext & A !== noContext; // true curContext & A === A
curContext & B !== noContext; // true curContext & B === B
// 退出上下文可以使用到按位与和按位非
let anotherContext = curContext;
anotherContext &= ~A;
anotherContext &= ~B;
// 与上面相同的方式判断上下文
anotherContext & A === noContext; // true anotherContext & A === 0;
anotherContext & B === noContext; // true anotherContext & B === 0;
当业务中存在多种状态的时候,就可以使用这种方式,类似之前在判断相同businessKey
时,不同的红点推送类型一样,当
businessKey === 'schedule'
时,不同的code
对应了不同的推送更新,假设code === 8
代表红点显示,
code === 16
代表弹出弹窗,code === 32
代表红点消失,code === 64
代表隐藏弹窗,那么重新设计状态判断逻辑,
则可以这么写:
const EMPTY_STATUS = 0;
let initialRedot = 0;
const SHOW_REDOT = 8;
const HIDE_REDOT = 32;
const SHOW_ALERT = 16;
const HIDE_ALERT = 64;
// mock推送数据为红点显示
const content01 = {
code: 8,
businessKey: 'schedule',
title: 'show redot'
};
initialRedot |= content01.code;
// isShowRedot === true
const isShowRedot = (initialRedot & SHOW_REDOT) !== EMPTY_STATUS; // tips: 这里不加括号会有运算符优先级问题,好烦……
// mock推送数据为红点显示
const content02 = {
code: 32,
businessKey: 'schedule',
title: 'hide redot'
};
initialRedot |= content02.code;
// isHideRedot === true
const isHideRedot = (initialRedot & HIDE_REDOT) !== EMPTY_STATUS;
// mock推送数据为红点显示
const content03 = {
code: 16,
businessKey: 'schedule',
title: 'show alert'
};
initialRedot |= content03.code;
// isShowAlert === true
const isShowAlert = (initialRedot & SHOW_ALERT) !== EMPTY_STATUS;
// mock推送数据为红点显示
const content04 = {
code: 64,
businessKey: 'schedule',
title: 'hide alert'
};
initialRedot |= content04.code;
// isHideAlert === true
const isHideAlert = (initialRedot & HIDE_ALERT) !== EMPTY_STATUS;
优先级计算
先看一段React
中的源码,如下:
function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
在听别人解释它之前,先操作一番探索一下它的作用。React
中使用Int32
来保存更新,Int32
的最高位是符号位,所以
剩余的31位是具体的数字,处于越低的比特位,越需要优先处理。
上述的代码中 -lanes
本质上可以看做两步操作:
- lanes按位取反
- 取反后加一
const lanes = 1;
-lanes === ~lanes + 1; // true
再使用lanes
与本身取反后加一的值进行按位与操作,就可以得到原值最低位的数,拆解步骤如下:
const testV = 0b1010; // 十进制值为10 其最低位也就是最高优先级的位为10 十进制值为2
const testV1 = ~testV + 1; // 0101 + 1 === 0110;
const testV2 = testV & testV1; // 1010 & 0110 === 0010 最终结果为10,十进制值Wie2,与预期相符合
// React中的优先级常量
var NoLane = 0;
var SyncLane = 1;
var InputContinuousHydrationLane = 2;
var InputContinuousLane = 4;
var DefaultHydrationLane = 8;
var DefaultLane = 16;
var TransitionHydrationLane = 32;
var TransitionLanes = 4194240;
var TransitionLane1 = 64;
var TransitionLane2 = 128;
var TransitionLane3 = 256;
var TransitionLane4 = 512;
var TransitionLane5 = 1024;
var TransitionLane6 = 2048;
var TransitionLane7 = 4096;
var TransitionLane8 = 8192;
var TransitionLane9 = 16384;
var TransitionLane10 = 32768;
var TransitionLane11 = 65536;
var TransitionLane12 = 131072;
var TransitionLane13 = 262144;
var TransitionLane14 = 524288;
var TransitionLane15 = 1048576;
var TransitionLane16 = 2097152;
var RetryLanes = 130023424;
var RetryLane1 = 4194304;
var RetryLane2 = 8388608;
var RetryLane3 = 16777216;
var RetryLane4 = 33554432;
var RetryLane5 = 67108864;
var SomeRetryLane = RetryLane1;
var SelectiveHydrationLane = 134217728;
var NonIdleLanes =
268435455;
var IdleHydrationLane = 268435456;
var IdleLane = 536870912;
var OffscreenLane = 1073741824;
// 使用上述优先级值做一些测试
var lanes = 0;
lanes |= SyncLane; // 同步优先级,为最高优先级
lanes |= OffscreenLane; // offScreeenLane是一个还未在正式环境启用的优先级,目前来看是最低的
// 此时想获取lanes中最高优先级值,只需要拿到其最低位的二进制数即可
var result = lanes & -lanes; // guess what??? 居然是1诶
总结
虽然位操作在日常的业务开发中很少用到,但是在React这种Library中性能还是需要做到能省则省的,以及毕竟后面还是要看 源码的,作为预备知识也还是相当必要的。