2023-08-10-关于位运算

位运算

Posted by EWL on August 10, 2023

几个(不太 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 本质上可以看做两步操作:

  1. lanes按位取反
  2. 取反后加一
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中性能还是需要做到能省则省的,以及毕竟后面还是要看 源码的,作为预备知识也还是相当必要的。