Published on

位运算

Authors
  • avatar
    Name
    李丹秋
    Twitter

React中常见的位运算方法 (05-31)

// 检测两个位标志之间是否有相同的标志,大部分的lane(非合成lane)只占据一个bit位,所以除非两个lane完全相同,否则不会有相同的标志
export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {
  return (a & b) !== NoLanes;
}

React中的优先级

位运算

运算符用法描述
与 &a & b位与运算符,两个操作数中对应的位都是1时,结果才为1,否则为0
非 ~~a按位取反运算符,对数据的每个二进制位取反,即把1变为0,把0变为1
或 |a | b位或运算符,两个二进制位中只要有一个为1,结果就是1,否则为0
异或 ^a ^ b异或运算符,两个操作数的位中,相同则结果为0,不同则结果为1
右移 >>a >> b右移运算符,将一个数的二进制位全部右移若干位,保持符号位不变,高位补0
无符号右移 >>>a >>> b无符号右移运算符,将一个数的二进制位全部右移若干位,高位补0
取反 ^a ^ b异或运算符,两个操作数的位中,相同则结果为0,不同则结果为1
与赋值 &=a &= b与赋值运算符,与运算后赋值给左操作数
或赋值 |=a |= b或赋值运算符,或运算后赋值给左操作数
异或赋值 ^=a ^= b异或赋值运算符,异或运算后赋值给左操作数
右移赋值 >>=a >>= b右移赋值运算符,右移后赋值给左操作数
无符号右移赋值 >>>=a >>>= b无符号右移赋值运算符,无符号右移后赋值给左操作数

更新优先级

React 中是存在不同优先级的任务的,比如用户文本框输入内容,需要 input 表单控件,如果控件是受控的(受数据驱动更新视图的模式),也就是当我们输入内容的时候,需要改变 state 触发更新,在把内容实时呈现到用户的界面上,这个更新任务就比较高优先级的任务。

相比表单输入的场景,比如一个页面从一个状态过渡到另外一个状态,或者一个列表内容的呈现,这些视觉的展现,并不要求太强时效性,期间还可能涉及到与服务端的数据交互,所以这个更新,相比于表单输入,就是一个低优先级的更新。

如果一个用户交互中,仅仅出现一个更新任务,那么 React 只需要公平对待这些更新就可以了。 但是问题是可能存在多个更新任务,举一个例子:远程搜索功能,当用户输入内容,触发列表内容的变化,这个时候如果把输入表单和列表更新放在同一个优先级,无论在 js 执行还是浏览器绘制,列表更新需要的时间远大于一个输入框更新的时间,所以输入框频繁改变内容,会造成列表频繁更新,列表的更新会阻塞到表单内容的呈现,这样就造成了用户不能及时看到输入的内容,造成了一个很差的用户体验。

所以 React 解决方案就是多个更新优先级的任务存在的时候,高优先级的任务会优先执行,等到执行完高优先级的任务,在回过头来执行低优先级的任务,这样保证了良好的用户体验。这样就解释了为什么会存在不同优先级的任务,那么 React 用什么标记更新的优先级呢?

export const NoLanes = /*                        */ 0b0000000000000000000000000000000;
const SyncLane = /*                        */ 0b0000000000000000000000000000001;

const InputContinuousHydrationLane = /*    */ 0b0000000000000000000000000000010;
const InputContinuousLane = /*             */ 0b0000000000000000000000000000100;

const DefaultHydrationLane = /*            */ 0b0000000000000000000000000001000;
const DefaultLane = /*                     */ 0b0000000000000000000000000010000;

const TransitionHydrationLane = /*                */ 0b0000000000000000000000000100000;
const TransitionLane = /*                        */ 0b0000000000000000000000001000000;

function getHighestPriorityLanes(lanes) {
   /* 通过 getHighestPriorityLane 分离出优先级高的任务 */ 
  switch (getHighestPriorityLane(lanes)) {
       case SyncLane:
         return SyncLane;
       case InputContinuousHydrationLane:
         return InputContinuousHydrationLane;
       ...  
  }

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

const SyncLane = 0b0000000000000000000000000000001
const InputContinuousLane = 0b0000000000000000000000000000100
const lane = SyncLane | InputContinuousLane
console.log( (lane & -lane) === SyncLane  ) // true

更新标识flag

export const NoFlags = /*                      */ 0b00000000000000000000000000;
export const PerformedWork = /*                */ 0b00000000000000000000000001;
export const Placement = /*                    */ 0b00000000000000000000000010;
export const Update = /*                       */ 0b00000000000000000000000100;
export const Deletion = /*                     */ 0b00000000000000000000001000;
export const ChildDeletion = /*                */ 0b00000000000000000000010000;
export const ContentReset = /*                 */ 0b00000000000000000000100000;
export const Callback = /*                     */ 0b00000000000000000001000000;
export const DidCapture = /*                   */ 0b00000000000000000010000000;
export const ForceClientRender = /*            */ 0b00000000000000000100000000;
export const Ref = /*                          */ 0b00000000000000001000000000;
export const Snapshot = /*                     */ 0b00000000000000010000000000;
export const Passive = /*                      */ 0b00000000000000100000000000;
export const Hydrating = /*                    */ 0b00000000000001000000000000;
export const Visibility = /*                   */ 0b00000000000010000000000000;
export const StoreConsistency = /*             */ 0b00000000000100000000000000;

比如一些小朋友在做一个寻宝的游戏,在沙滩中埋了很多宝藏,有专门搜索这些宝藏的仪器,也有挖这些宝藏的工具,那么小朋友中会分成两组,一组负责拿仪器寻宝,另外一组负责挖宝,寻宝的小朋友在前面,找到宝藏之后不去直接挖,而是插上小旗子 (flags) 证明这个地方有宝藏,接下来挖宝的小朋友统一拿工具挖宝。这个流程非常高效,把不同的任务分配给不同的小朋友,各尽其职。

React 的更新流程和如上这个游戏如出一撤,也是分了两个阶段,第一个阶段就像寻宝的小朋友一样,找到待更新的地方,设置更新标志 flags,接下来在另一个阶段,通过 flags 来证明当前 fiber 发生了什么类型的更新,然后执行这些更新。

const NoFlags = 0b00000000000000000000000000;
const PerformedWork =0b00000000000000000000000001;
const Placement =  0b00000000000000000000000010;
const Update = 0b00000000000000000000000100;
//初始化
let flag = NoFlags

//发现更新,打更新标志
flag = flag | PerformedWork | Update

//判断是否有  PerformedWork 种类的更新
if(flag & PerformedWork){
    //执行
    console.log('执行 PerformedWork')
}

//判断是否有 Update 种类的更新
if(flag & Update){
    //执行
    console.log('执行 Update')
}


if(flag & Placement){
    //不执行
    console.log('执行 Placement')
}

如上会打印 执行 PerformedWork ,上面的流程清晰的描述了在 React 打更新标志,又如何判断更新类型的。