react 源码中位运算符的使用

位运算符基本使用

  • 按位与(&):a & b对于每一个比特位,两个操作数都为 1 时, 结果为 1, 否则为 0
  • 按位或(|):a | b对于每一个比特位,两个操作数都为 0 时, 结果为 0, 否则为 1
  • 按位异或(^):a ^ b对于每一个比特位,两个操作数相同时结果为0, 否则为 1
  • 按位非(~):~ a反转操作数的比特位, 即 0 变成 1, 1 变成 0
0000 0000 0000 0000 0000 0000 0000 0011     -> 3
1111 1111 1111 1111 1111 1111 1111 1100     -> ~ 3 = -4

  • 左移位<<:各二进位全部左移若干位,高位丢弃,低位补0。
0000 0000 0000 0000 0000 0000 0000 0110     -> 6
0000 0000 0000 0000 0000 0000 0001 1000     -> 6 << 2 = 24

  • 右移位>>:各二进位全部右移若干位,正数高位补0,负数高位补1,低位丢弃
0000 0000 0000 0000 0000 0000 0000 1100     -> 12
0000 0000 0000 0000 0000 0000 0000 0011     -> 12 >> 2 = 3

负数情况:
1111 1111 1111 1111 1111 1111 1111 0100    -> -12
1111 1111 1111 1111 1111 1111 1111 1101    -> -12 >> 2 = -3
  • 无符号右移位>>>:各二进位全部右移若干位,高位补0,低位丢弃。
0000 0000 0000 0000 0000 0000 0000 1100     -> 12
0000 0000 0000 0000 0000 0000 0000 0011     -> 12 >>> 2 = 3

1111 1111 1111 1111 1111 1111 1111 0100    -> -12
0011 1111 1111 1111 1111 1111 1111 1101    -> -12 >> 2 = 1073741821

正数计算负数:

  • 取反后+1
501015:取反1010 最低位+1 = 10111011即为二进制的-5

负数倒推正数:同样为取反后+1

在js中位运算的特点

  • 位运算只能在整型变量之间进行运算
  • js 中的Number类型在底层都是以浮点数(参考 IEEE754 标准)进行存储.
  • js 中所有的按位操作符的操作数都会被转成补码(two’s complement)形式的有符号32位整数
  • 操作数为浮点型时,转换流程: 浮点数 -> 整数(丢弃小数位) -> 位运算
  • 操作数的大小超过Int32范围(-2^31 ~ 2^31-1). 超过范围的二进制位会被截断, 取低位32bit
  • 另外由于 js 语言的隐式转换, 对非Number类型使用位运算操作符时会隐式会发生隐式转换, 相当于先使用Number(xxx)将其转换为number类型, 再进行位运算:
'str' >>> 0; //  ===> Number('str') >>> 0  ===> NaN >>> 0 = 0

位掩码

  • 通过位移定义的一组枚举常量, 可以利用位掩码的特性, 快速操作这些枚举产量(增加, 删除, 比较)
const A = 1 << 0; // 0b00000001
const B = 1 << 1; // 0b00000010
const C = 1 << 2; // 0b00000100

属性增加|
ABC = A | B | C //0b00000111

属性删除& ~
AB = ABC & ~C //0b00000011

属性比较
AB 当中包含 B: AB & B === B// AB & B =>0b00000010 ===B,true
AB 当中不包含 C: AB & C === 0 // AB & C =>0b00000000 === 0,true

通过^交换数据值

  • 由^概念知:0^n=n; n^n=0;
  • ^满足交换律和结合律
  • 好处:不会开辟新的内存

提取最右边第一个位为1的二进制

// 最终结果类似...000100...
let rightestOne = eor & (~eor + 1);
int a=3,b=2;
a=a^b; 
b=a^b; //b=a^b^b=a^0=a; 固b=a;
a=a^b; //a=a^b(旧b)^b(新b); 此时新b在上一步后和a相等,固a=0^b(旧b)=b(旧b)
//a:2,b:3

react中的位运算

  • react在涉及状态、标记位、优先级操作的地方大量使用了位运算

标记状态

  • react源码内部有多个上下文环境,在执行函数时经常需要判断当前处在哪个上下文环境中
// A上下文
const A = 1; //0001
// B上下文
const B = 2; //0010
// 当前所处上下文
let curContext = 0;
// 没有处在上下文的标志
const NoContext = 0;

假设进入A的上下文
curContext |= A; //即curContext=curContext|A =>0001


判断是否处在某一上下文中,结合按位与操作与NoContext
// 是否处在A上下文中,这里为true
(curContext & A) !== NoContext //curContext & A)=>0001 !==0000,所以为true,表示在A的上下文中

// 是否处在B上下文中,这里为false,和上方同理
(curContext & B) !== NoContext 

离开上下文,取出标记进行恢复
// 从当前上下文中移除上下文A
curContext &= ~A; //curContext=curContext& ~A,即0001&1110=0000,进行恢复

// 是否处在A上下文中,此处为false
(curContext & A) !== NoContext //(curContext & A)为0000 

ReactFiberLane.js

  • 优先级定义
  • 源码中变量只列出了 31 位, 由于 js 中位运算都会转换成Int32(上文已经解释), 最多为 32 位, 且最高位是符号位. 所以除去符号位, 最多只有 31 位可以参与运算
//类型定义
export opaque type Lanes = number;
export opaque type Lane = number;

// 变量定义
export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;

export const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;

const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;
// ...
// ...

const NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;

export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;
const IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;

export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;

getHighestPriorityLanes

function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
  // 判断 lanes中是否包含 SyncLane
  if ((SyncLane & lanes) !== NoLanes) {
    return_highestLanePriority = SyncLanePriority;
    return SyncLane;
  }
  // 判断 lanes中是否包含 SyncBatchedLane
  if ((SyncBatchedLane & lanes) !== NoLanes) {
    return_highestLanePriority = SyncBatchedLanePriority;
    return SyncBatchedLane;
  }
  // ...
  // ... 省略其他代码
  return lanes;
}

getHighestPriorityLane

  • react中处在越低bit位的更新优先级越高(越需要优先处理)
  • 分离出最高优先级
  • -lanes:表示负数的操作,即先取反然后+1
0b000 0000 0000 0000 0000 0000 0001 0001

function getHighestPriorityLane(lanes) {
  return lanes & -lanes;
}

-lanse:
lanes  0001 0001
~lanes 1110 1110 // 第一步
+1     1110 1111 // 第二步

  0001 0001 // lanes  
& 1110 1111 // -lanes
-----------
  0000 0001

若lanes为0001 0000
  0001 0000 // lanes  
& 1111 0000 // -lanes
-----------
  0001 0000

getLowestPriorityLane

  • 假设 lanes(InputDiscreteLanes) = 0b0000000000000000000000000011000
  • 那么 clz32(lanes) = 27, 由于 InputDiscreteLanes 在源码中被书写成了 31 位, 虽然在字面上前导 0 是 26 个, 但是转成标准 32 位后是 27 个
  • index = 31 - clz32(lanes) = 4
  • 最后 1 << index = 0b0000000000000000000000000010000
  • 相比最初的 InputDiscreteLanes, 分离出来了最左边的1
  • 通过 lanes 的定义, 数字越小的优先级越高, 所以此方法可以获取最低优先级的 lane
function getLowestPriorityLane(lanes: Lanes): Lane {
  // This finds the most significant non-zero bit.
  const index = 31 - clz32(lanes);
  return index < 0 ? NoLanes : 1 << index;
}

react-reconciler上下文定义

export const NoContext = /*             */ 0b0000000;
const BatchedContext = /*               */ 0b0000001;
const EventContext = /*                 */ 0b0000010;
const DiscreteEventContext = /*         */ 0b0000100;
const LegacyUnbatchedContext = /*       */ 0b0001000;
const RenderContext = /*                */ 0b0010000;
const CommitContext = /*                */ 0b0100000;
export const RetryAfterError = /*       */ 0b1000000;

// ...

// Describes where we are in the React execution stack
let executionContext: ExecutionContext = NoContext;

scheduleUpdateOnFiber

// scheduleUpdateOnFiber函数中包含了好多关于executionContext的判断(都是使用位运算)
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  if (root === workInProgressRoot) {
    // 判断: executionContext 不包含 RenderContext
    if (
      deferRenderPhaseUpdateToNextBatch ||
      (executionContext & RenderContext) === NoContext
    ) {
      // ...
    }
  }
  if (lane === SyncLane) {
    if (
      // 判断: executionContext 包含 LegacyUnbatchedContext
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      // 判断: executionContext 不包含 RenderContext或CommitContext
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // ...
    }
  }
  // ...
}

执行批处理时上下文:

  • 通过闭包,在执行回调fn期间,无论函数fn调用栈有多深,被改变过的executionContext始终有效.
export function batchedUpdates<A, R>(fn: A => R, a: A): R {
  // 在执行回调之前, 先改变 executionContext
  //保存之前上下文
  const prevExecutionContext = executionContext;
  //将上下文变成BatchedContext
  executionContext |= BatchedContext;
  try {
    return fn(a);
  } finally {
    // 回调执行完毕之后, 再恢复到以前的值 prevExecutionContext
    executionContext = prevExecutionContext;
    // ... 省略无关代码
  }
}

export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
  const prevExecutionContext = executionContext;
  //从上下文中移除BatchedContext
  executionContext &= ~BatchedContext;
  //将上下文变成LegacyUnbatchedContext
  executionContext |= LegacyUnbatchedContext;
  try {
    return fn(a);
  } finally {
  	//恢复上下文
    executionContext = prevExecutionContext;
    // ... 省略无关代码
  }
}

// ... 省略其他函数

  • 在特定的情况下, 使用位运算不仅是提高运算速度, 且位掩码能简洁和清晰地表示出二进制变量之间的关系.
  • 但是缺点也很明显, 不够直观, 扩展性不好(在 js 当中的二进制变量, 除去符号位, 最多只能使用 31 位, 当变量的数量超过 31 个就需要组合, 此时就会变得复杂)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值