likes
comments
collection
share

【react18原理探究实践】JS中的位运算&react中的lane模型

作者站长头像
站长
· 阅读数 63

🧑‍💻 写在开头

点赞 + 收藏 === 学会🤣🤣🤣

🥑 你能学到什么?

希望在你阅读本篇文章之后,不会觉得浪费了时间。如果你跟着读下来,你将会学到:

  • 原码、反码、补码
  • JS中数值如何表示?
  • 位运算规则&场景
  • react的优先级调度机制
  • 优先级转换
  • lane车道模型

✍️系列文章react实现原理系列

一、原码、反码、补码

原码(Sign-Magnitude)

原码是一种直接表示整数的方法,其中最高位(最左边的位)表示符号,其余位表示数值的大小。对于 n 位的二进制数:

  • 最高位为 0 表示正数。
  • 最高位为 1 表示负数。 例如,对于 8 位表示法:
  • +5 的原码表示为:0000 0101
  • -5 的原码表示为:1000 0101

但是这种表示法有一个明显的缺点:存在两个 0,即 +00000 0000)和 -01000 0000)。

反码(One's Complement)

反码是一种在历史上曾使用过的表示方法,主要用于早期计算机。反码的表示规则:

  1. 正数的反码与原码相同。
  2. 负数的反码:将数值的绝对值按位取反(变 0 为 1,变 1 为 0)。 例如,对于 8 位表示法:
  • +5 的反码表示为:0000 0101
  • -5 的反码表示为:
    1. 取绝对值的二进制表示:0000 0101
    2. 按位取反:1111 1010 反码也存在两个 0:+00000 0000)和 -01111 1111)。反码在加减法运算时需要进行额外的处理,因此逐渐被补码取代。

补码(Two's Complement)

补码是现代计算机中广泛使用的表示带符号整数的方法。它解决了原码的冗余 0 和加减运算的复杂性。补码的特点是只有一个 0(0000 0000),并且负数的表示方式简化了计算。 补码的表示规则:

  1. 正数的补码与原码相同。
  2. 负数的补码:将数值的绝对值按位取反(变 0 为 1,变 1 为 0),然后加 1。 例如,对于 8 位表示法:
  • +5 的补码表示为:0000 0101
  • -5 的补码表示为:
    1. 取绝对值的二进制表示:0000 0101
    2. 按位取反:1111 1010
    3. 加 1:1111 1011

补码表示法使得加减法运算更为简便。例如,5 + (-5) 直接按位相加,结果为 0。

二、补码的计算:5 + (-5) 直接按位相加如何计算

这里用8位表示

步骤 1:表示 5-5

  • 5 的二进制表示(8 位):0000 0101
  • -5 的补码表示:
    1. 5 的二进制表示:0000 0101
    2. 按位取反:1111 1010
    3. 加 1:1111 1011

所以,-5 的补码表示为 1111 1011

步骤 2:按位相加 5-5

满2进1

  0000 0101  (5)
+ 1111 1011  (-5)
-------------
  1. 1 + 1 = 10,结果是 0,进位 1
  2. 0 + 1 + 1 = 10,结果是 0,进位 1
  3. 1 + 0 + 1 = 10,结果是 0,进位 1
  4. 0 + 1 + 1 = 10,结果是 0,进位 1
  5. 0 + 1 + 1 = 10,结果是 0,进位 1
  6. 0 + 1 + 1 = 10,结果是 0,进位 1
  7. 0 + 1 + 1 = 10,结果是 0,进位 1
  8. 0 + 1 = 1,结果是 1

这里需要注意最后超出8位的进位被忽略(在 8 位表示中)

  0000 0000  (结果为 0)

三、JS中数值如何表示?

在 js 中,所有的数值都是以双精度浮点数(64 位)形式存储的,是由 IEEE 754 标准定制的。这种存储方式既适用于整数,也适用于小数。这与其他许多编程语言不同,它区分整数类型和浮点数类型。

IEEE 754 双精度浮点数

简单了解下即可,IEEE 754 双精度浮点数格式由 64 位组成,分为三个部分:

  1. 符号位(1 位)(S) :表示数值的正负。0 表示正数,1 表示负数。
  2. 指数(11 位)(E) :指数位,表示数值的规模(即大小),用偏移量表示,即实际的指数值要减去一个偏移量(Bias)。
  3. 尾数(52 位)(M) :表示数值的精度,隐含一个前导的 1,即 1.M。。

【react18原理探究实践】JS中的位运算&react中的lane模型

偏移量(Bias)

对于 11 位的指数位,指数的偏移量是 1023。偏移量的作用是将指数范围从 [-1023, 1024] 映射到非负数范围 [0, 2047]。这是为了方便表示正数和负数的指数。

指数范围

  • 最小指数:-1023(指数位为全零,即 E = 0,但这是保留值,用于表示非规格化数和零)
  • 最大指数:1023(指数位为 2047,但这是保留值,用于表示无穷大和 NaN)

正常数(Normalized Numbers)

对于正常数,指数位的范围是 [1, 2046],实际的指数值是 E - 1023。

例如:

let num = 2.5; // 二进制表示:1.01 * 2^1
// 符号位 S = 0
// 指数位 E = 1024(实际指数 1 + 1023 偏移量)
// 尾数 M = 0100000000000000000000000000000000000000000000000000

非规格化数(Denormalized Numbers)

对于非规格化数,指数位 E = 0,此时的实际指数值是 1 - 1023 = -1022,尾数部分不再隐含前导 1。

例如:

let num = 1.0e-308; // 非规格化数,接近最小正数
// 符号位 S = 0
// 指数位 E = 0(实际指数 -1022)
// 尾数 M = 0000000000000000000000000000000000000000000000000001

为什么是 -1023?

  • 指数部分使用偏移量是为了方便表示正数和负数的指数。
  • 偏移量选择 1023 是因为 1023 是 11 位二进制数的中间值,使得指数既可以表示正数,也可以表示负数。
  • 这样设计可以确保浮点数的表示范围既包括非常大的正数,也包括非常小的负数,同时还能表示接近零的小数值。

通过这种方式,浮点数可以表示的范围非常广泛,从非常大的正数到非常小的正数(接近零),以及对应的负数,满足了大多数计算需求。

特殊数值

  • 0:有两种表示,正零和负零(+0 和 -0),但它们在比较时是相等的。
  • NaN:表示“不是一个数字”(Not-a-Number),用于表示无法定义的或未表示的数值结果,例如 0 / 0
  • Infinity-Infinity:表示正无穷大和负无穷大,例如 1 / 0

四、位运算

JS中的位运算(bitwise operations)是对数值的二进制表示进行操作的运算。它们通常用于底层编程、性能优化以及一些算法中。以下是JS中的一些常见位运算:

位运算符&运算规则

  1. 按位与(AND)&

    • 对应位都为1时,结果才为1。
    • 示例:5 & 3(即 0101 & 0011)结果是 0001,即 1
  2. 按位或(OR)|

    • 对应位只要有一个为1,结果就是1。
    • 示例:5 | 3(即 0101 | 0011)结果是 0111,即 7
  3. 按位异或(XOR)^

    • 对应位不同,结果为1;相同,结果为0。
    • 示例:5 ^ 3(即 0101 ^ 0011)结果是 0110,即 6
  4. 按位非(NOT)~

    • 将数值的每一位取反,即0变1,1变0。
    • 示例:~5(即 ~0101)结果是 1010,即 -6(按补码表示)。
  5. 左移(Left Shift)<<

    • 将数值的二进制位左移,右边补0。
    • 示例:5 << 1(即 0101 << 1)结果是 1010,即 10
  6. 右移(Signed Right Shift)>>

    • 将数值的二进制位右移,左边补符号位(即正数补0,负数补1)。
    • 示例:5 >> 1(即 0101 >> 1)结果是 0010,即 2
  7. 无符号右移(Unsigned Right Shift)>>>

    • 将数值的二进制位右移,左边补0(不考虑符号位)。
    • 示例:-5 >>> 1(即 11111111111111111111111111111011 >>> 1)结果是 01111111111111111111111111111101,即 2147483645

位运算细节

操作数转换为有符号32位整数

在进行位运算之前,js 会将操作数转换为有符号32位整数。即使是浮点数或超过32位范围的整数,也会在进行位运算之前被截取并转换为32位整数。这是通过 ToInt32 函数实现的。

ToInt32 函数

ToInt32 函数将数值转换为有符号32位整数,具体步骤如下:

  • 将非数值类型转换为数值类型:如布尔值、字符串等。
  • 将数值转换为整数:去掉小数部分。
  • 取模 2^32:即数值被限制在32位范围内。
  • 将结果映射到有符号整数范围:[-2^31, 2^31 - 1]。

举例

let a = 5.7;
let b = -5.7;
let c = 4294967300; // 超过 32 位范围的整数

console.log(a | 0); // 输出 5
console.log(b | 0); // 输出 -5
console.log(c | 0); // 输出 4 (4294967300 % 2^32 = 4)


let x = 5.7;     // 二进制: 00000000000000000000000000000101
let y = -5.7;    // 二进制: 11111111111111111111111111111011

let result = x | y; // 位运算: 00000000000000000000000000000101 | 11111111111111111111111111111011 = 11111111111111111111111111111111
console.log(result); // 输出 -1 (补码表示)

let largeNum = 4294967300; // 大数, 超过 32 位
let result2 = largeNum | 0; // 位运算后,取模并转换为 32 位: 00000000000000000000000000000100
console.log(result2); // 输出 4

  • 位运算只能在整型变量之间进行运算:浮点数和其他非整型操作数在运算前会被转换为整数。
  • js中的数值类型在底层都是以浮点数(符合 IEEE 754 标准)存储:但位运算会将它们转换为32位有符号整数。
  • 操作数被转换为补码(Two's Complement)形式的有符号32位整数:进行位运算,结果也是32位有符号整数。

转为32位的原因

将位运算限制为32位整数主要是为了简化计算并提高效率。以下是一些原因:

1. 硬件限制与兼容性

早期计算机硬件和处理器大多是基于32位架构设计的,处理32位整数运算在硬件层面上具有更高的效率。即使现代处理器支持64位或更高的计算能力,许多基础运算和寄存器仍然保持对32位整数运算的优化和支持。

2. 简化实现

限制位运算为32位整数可以简化js引擎的实现。在进行位运算时,不需要处理超出32位范围的复杂情况,这有助于提高运算速度并减少错误。例如,当一个值超过32位时,只需取模 2^32 即可。

3. 避免浮点数的不精确性

js 中的数值类型(Number)基于IEEE 754双精度浮点数表示法。这种表示法虽然可以表示非常大的数和非常小的小数,但在处理整数时存在精度问题。例如,超过53位的整数无法精确表示。

通过将位运算限制在32位整数范围内,可以避免浮点数表示法带来的不精确性,确保位运算结果的准确性。

4. 语言设计选择

许多编程语言,包括C、C++、Java等,都将位运算限制在32位整数范围内。这种限制已被广泛接受并用于各种应用场景。JavaScript 作为一种通用编程语言,遵循了这种设计选择,以确保与其他语言和系统的兼容性。

五、位运算场景

1. 位掩码(Bit Masks)

位掩码用于检查、设置、清除和切换特定位的值,react源码中lane车道模型就用到了这种方法。例如:

const READ = 1; // 0001
const WRITE = 2; // 0010
const EXECUTE = 4; // 0100

let permissions = READ | WRITE; // 0011

// 检查是否具有写权限:是否包含写,可以视为集合包含关系
// 主要是从计算规则上来的,只有1&1才为1,所以能判断是否包含
if (permissions & WRITE) {
  console.log("Has write permission");
}

// 添加执行权限,1 | 1 = 1, 1 | 0 = 1,这样结果中对应位就添加上了
permissions |= EXECUTE; // 0111

// 移除写权限,0111 & 1101 = 0101,这样写的位置就为0了
permissions &= ~WRITE; // 0101

2.位字段(Bit Fields)

位字段用于紧凑地存储多个布尔值或小范围的整数。例如:

const flags = {
  flag1: 1 << 0, // 0001
  flag2: 1 << 1, // 0010
  flag3: 1 << 2, // 0100
};

let bitField = 0;
bitField |= flags.flag1; // 设置 flag1
bitField |= flags.flag2; // 设置 flag2

// 检查 flag2 是否设置
if (bitField & flags.flag2) {
  console.log("flag2 is set");
}

// 清除 flag1
bitField &= ~flags.flag1;

3.整数溢出和环绕(Overflow and Wraparound)

位运算可用于实现整数溢出和环绕。例如:

let a = (1 << 31) - 1; // 最大 32 位有符号整数
let b = 1;
let c = (a + b) & 0xFFFFFFFF; // 溢出处理
console.log(c); // 输出 -2147483648

4.颜色处理

位运算可用于处理和操作颜色值。例如,将 RGB 颜色值合并为一个整数:

function rgbToInt(r, g, b) {
  return (r << 16) | (g << 8) | b;
}

let color = rgbToInt(255, 165, 0); // 合并为橙色 (255, 165, 0)
console.log(color.toString(16)); // 输出 'ffa500'

5.数组访问优化

位运算可用于加速数组访问或索引计算。例如,在环形缓冲区中:

const bufferSize = 8;
const buffer = new Array(bufferSize);
let writeIndex = 0;
let readIndex = 0;

function write(value) {
  buffer[writeIndex] = value;
  writeIndex = (writeIndex + 1) & (bufferSize - 1); // 环绕
}

function read() {
  const value = buffer[readIndex];
  readIndex = (readIndex + 1) & (bufferSize - 1); // 环绕
  return value;
}

6.性能优化

位运算通常比高层次的运算(如乘法、除法、求余等)更快,因为它们直接映射到处理器的指令集上。例如,使用位移操作实现快速乘法或除法:

let x = 5;
let result1 = x << 1; // 等价于 x * 2,结果为 10
let result2 = x >> 1; // 等价于 x / 2,结果为 2

7.判断奇偶性

通过位运算判断一个数是奇数还是偶数:

let num = 10;
if (num & 1) {
  console.log("奇数");
} else {
  console.log("偶数");
}

8.状态表示

位字段可以用于表示对象或系统的多个状态。

const STATE_READY = 1 << 0; // 0001
const STATE_RUNNING = 1 << 1; // 0010
const STATE_PAUSED = 1 << 2; // 0100
const STATE_FINISHED = 1 << 3; // 1000

let state = STATE_READY | STATE_RUNNING; // 对象的状态为 READY 和 RUNNING

if (state & STATE_RUNNING) {
  console.log("The system is running");
}

state |= STATE_PAUSED; // 将状态设置为 PAUSED
state &= ~STATE_RUNNING; // 清除 RUNNING 状态

9.多优先级对列

比如用8位来表示优先级,从低到高,就可以用位掩码方式,赋予&去除&获取最高优先级等等

六、react中的优先级机制&位运算使用

优先级机制最终目的是为了实现高优先级任务优先执行,低优先级任务延后执行。实现这一目的的本质就是在低优先级任务执行时,有更高优先级任务进来的话,可以打断低优先级任务的执行。

1.确定优先级表示

区分优先级:react 中主要分为两类优先级

  • scheduler 优先级:主要用在时间分片中任务过期时间的计算
  • lane 优先级:lane优先级下面又派生出 event 优先级
    • lane 优先级:主要用于任务调度前,对当前正在进行的任务和被调度任务做一个优先级校验,判断是否需要打断当前正在进行的任务
    • event 优先级:本质上也是lane优先级,lane优先级是通用的,event优先级更多是结合浏览器原生事件,对lane优先级做了分类和映射

【react18原理探究实践】JS中的位运算&react中的lane模型

2.scheduler 优先级

优先级级别默认数值版本大于'17.0.2'时的数值使用情况
ImmediatePriority991用于需要立即执行的任务,如紧急更新
UserBlockingPriority982用于需要快速响应用户交互的任务
NormalPriority973用于大多数日常任务
LowPriority964用于不需要紧急处理的任务
IdlePriority955用于浏览器空闲时才执行的任务
NoPriority900用于不指定优先级的任务
// ReactPriorityLevels定义了React任务的优先级别及其相应的数值
let ReactPriorityLevels: ReactPriorityLevelsType = {
  ImmediatePriority: 99,      // 立即优先级: 用于需要立即执行的任务,如紧急更新
  UserBlockingPriority: 98,   // 用户阻塞优先级: 用于需要快速响应用户交互的任务
  NormalPriority: 97,         // 普通优先级: 用于大多数日常任务
  LowPriority: 96,            // 低优先级: 用于不需要紧急处理的任务
  IdlePriority: 95,           // 空闲优先级: 用于浏览器空闲时才执行的任务
  NoPriority: 90,             // 无优先级: 用于不指定优先级的任务
};

// 检查React版本,如果版本大于'17.0.2',则使用不同的优先级数值
if (gt(version, '17.0.2')) {
  ReactPriorityLevels = {
    ImmediatePriority: 1,       // 立即优先级: 用于需要立即执行的任务,如紧急更新
    UserBlockingPriority: 2,    // 用户阻塞优先级: 用于需要快速响应用户交互的任务
    NormalPriority: 3,          // 普通优先级: 用于大多数日常任务
    LowPriority: 4,             // 低优先级: 用于不需要紧急处理的任务
    IdlePriority: 5,            // 空闲优先级: 用于浏览器空闲时才执行的任务
    NoPriority: 0,              // 无优先级: 用于不指定优先级的任务
  };
}

3.lane 优先级

可以用赛道的概念去理解lane优先级,lane优先级有31个,我们可以用31位的二进制值去表示,值的每一位代表一条赛道对应一个lane优先级,赛道位置越靠前,优先级越高,可以类比多个优先级队列,我们通过lane去管理多个优先级队列,一个赛道是就是一个具体优先级的队列,lane车道模型就是对多个优先级队列管理的一套机制 包含31个车道的名称、优先级和使用时机:

车道名称优先级使用时机
SyncLane1立即处理的任务,如用户点击和按键等离散事件。
InputContinuousHydrationLane2持续输入事件的水合任务,如滚动和拖动。
InputContinuousLane3持续输入事件,如滚动和拖动。
DefaultHydrationLane4一般水合任务。
DefaultLane5常规任务。
TransitionHydrationLane6过渡任务的水合。
TransitionLane17过渡任务1。
TransitionLane28过渡任务2。
TransitionLane39过渡任务3。
TransitionLane410过渡任务4。
TransitionLane511过渡任务5。
TransitionLane612过渡任务6。
TransitionLane713过渡任务7。
TransitionLane814过渡任务8。
TransitionLane915过渡任务9。
TransitionLane1016过渡任务10。
TransitionLane1117过渡任务11。
TransitionLane1218过渡任务12。
TransitionLane1319过渡任务13。
TransitionLane1420过渡任务14。
TransitionLane1521过渡任务15。
TransitionLane1622过渡任务16。
RetryLane123需要重试的任务1。
RetryLane224需要重试的任务2。
RetryLane325需要重试的任务3。
RetryLane426需要重试的任务4。
RetryLane527需要重试的任务5。
SelectiveHydrationLane28选择性水合任务。
IdleHydrationLane29空闲时的水合任务。
IdleLane30空闲时处理的任务。
OffscreenLane31离屏任务,例如后台渲染。
  1. 高优先级车道(1-5):用于处理需要立即响应的任务。
  2. 中等优先级车道(6-27):用于处理过渡任务和需要重试的任务。
  3. 低优先级车道(28-31):用于选择性水合任务、空闲任务和离屏任务。

这种分类有助于在不同情况下合理地分配任务,确保用户交互的流畅性和系统资源的高效利用。

// 定义总车道数
export const TotalLanes = 31;

// 定义没有车道的情况,用于表示不分配任何任务的状态
export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

// 定义同步车道,用于立即处理的任务
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;

// 定义输入连续(水合)车道,用于处理需要持续响应的用户输入任务
export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000000100;

// 定义默认(水合)车道,用于处理一般的水合任务
export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000001000;
export const DefaultLane: Lane = /*                     */ 0b0000000000000000000000000010000;

// 定义转换(Transition)车道,用于处理需要过渡的任务
const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /*                       */ 0b0000000001000000000000000000000;

// 定义重试(Retry)车道,用于处理需要重试的任务
const RetryLanes: Lanes = /*                            */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /*                             */ 0b0000100000000000000000000000000;

// 定义选择性水合车道,用于选择性地处理水合任务
export const SomeRetryLane: Lane = RetryLane1;
export const SelectiveHydrationLane: Lane = /*          */ 0b0001000000000000000000000000000;

// 定义非空闲车道,用于处理所有非空闲的任务
const NonIdleLanes: Lanes = /*                          */ 0b0001111111111111111111111111111;

// 定义空闲(水合)车道,用于处理空闲时的水合任务
export const IdleHydrationLane: Lane = /*               */ 0b0010000000000000000000000000000;
export const IdleLane: Lane = /*                        */ 0b0100000000000000000000000000000;

// 定义离屏车道,用于处理离屏任务
export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;

4.event 优先级

事件优先级(Event Priority) : 事件优先级是开发者为不同类型的事件分配的优先级,用于确定这些事件在React调度器中的处理顺序。例如,离散事件、连续事件、默认事件和空闲事件。

事件优先级通过具体的车道值来表示和实现。每种事件优先级对应一个特定的车道值。

// 定义并导出不同类型事件的优先级常量
// DiscreteEventPriority: 离散事件的优先级
// 离散事件是指那些发生在特定时刻的事件,例如点击、按键等。
// 这些事件需要立即响应。
export const DiscreteEventPriority: EventPriority = SyncLane;

// ContinuousEventPriority: 连续事件的优先级
// 连续事件是指那些持续发生的事件,例如滚动、拖动等。
// 这些事件需要在整个事件过程中保持响应。
export const ContinuousEventPriority: EventPriority = InputContinuousLane;

// DefaultEventPriority: 默认事件的优先级
// 默认事件是指没有特别优先级的普通事件。
// 用于处理一般的任务或事件,没有特别紧急或持续性的需求。
export const DefaultEventPriority: EventPriority = DefaultLane;

// IdleEventPriority: 空闲事件的优先级
// 空闲事件是指那些可以在浏览器空闲时处理的事件。
// 用于处理那些可以延迟执行的任务或事件,例如日志记录或非关键性更新。
export const IdleEventPriority: EventPriority = IdleLane;

5.优先级转换

lane优先级 转 event优先级

  • lane优先级 转 event优先级(参考 lanesToEventPriority 函数)

    • 转换规则:以区间的形式根据传入的lane返回对应的 event 优先级。比如传入的优先级不大于 Discrete 优先级,就返回 Discrete 优先级,以此类推
/**
 * 将车道转换为事件优先级。
 * @param lanes 车道,表示待处理的任务。
 * @returns 返回对应的事件优先级,用于调度任务。
 */
export function lanesToEventPriority(lanes: Lanes): EventPriority {
  // 获取最高优先级的车道
  const lane = getHighestPriorityLane(lanes);
  
  // 如果最高优先级的车道不高于离散事件优先级,则返回离散事件优先级
  if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
    return DiscreteEventPriority;
  }

  // 如果最高优先级的车道不高于连续事件优先级,则返回连续事件优先级
  if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
    return ContinuousEventPriority;
  }

  // 如果最高优先级的车道包含非空闲任务,则返回默认事件优先级
  if (includesNonIdleWork(lane)) {
    return DefaultEventPriority;
  }

  // 否则返回空闲事件优先级
  return IdleEventPriority;
}

event优先级 转 scheduler优先级

事件优先级处理的事件类型调度器优先级
DiscreteEventPriority离散事件,如点击、按键等ImmediateSchedulerPriority
ContinuousEventPriority连续事件,如滚动、拖动等UserBlockingSchedulerPriority
DefaultEventPriority默认事件,一般任务NormalSchedulerPriority
IdleEventPriority空闲事件,空闲任务IdleSchedulerPriority
默认情况未知类型事件NormalSchedulerPriority
// Determine the next lanes to work on, and their priority.
// 确定要处理的下一个 lanes 及其优先级。
const nextLanes = getNextLanes(
  root,
  root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);

// Convert event priority to scheduler priority.
// 将事件优先级转换为调度器优先级。
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
  case DiscreteEventPriority:
    schedulerPriorityLevel = ImmediateSchedulerPriority;
    break;
  case ContinuousEventPriority:
    schedulerPriorityLevel = UserBlockingSchedulerPriority;
    break;
  case DefaultEventPriority:
    schedulerPriorityLevel = NormalSchedulerPriority;
    break;
  case IdleEventPriority:
    schedulerPriorityLevel = IdleSchedulerPriority;
    break;
  default:
    schedulerPriorityLevel = NormalSchedulerPriority;
    break;
}

// Schedule a new callback with the converted priority.
// 使用转换后的优先级调度一个新的回调。
newCallbackNode = scheduleCallback(
  schedulerPriorityLevel,
  performConcurrentWorkOnRoot.bind(null, root),
);

event优先级 转 lane优先级

转换规则:对于非离散、连续的事件,会根据一定规则作转换.

事件优先级处理的事件类型
DiscreteEventPrioritycancel, click, close, ...
ContinuousEventPrioritydrag, dragenter, ...
DefaultEventPrioritymessage
IdleEventPriority默认情况
从源码定义中即可看出
// 定义并导出不同类型事件的优先级常量
// DiscreteEventPriority: 离散事件的优先级
// 离散事件是指那些发生在特定时刻的事件,例如点击、按键等。
// 这些事件需要立即响应。
export const DiscreteEventPriority: EventPriority = SyncLane;

// ContinuousEventPriority: 连续事件的优先级
// 连续事件是指那些持续发生的事件,例如滚动、拖动等。
// 这些事件需要在整个事件过程中保持响应。
export const ContinuousEventPriority: EventPriority = InputContinuousLane;

// DefaultEventPriority: 默认事件的优先级
// 默认事件是指没有特别优先级的普通事件。
// 用于处理一般的任务或事件,没有特别紧急或持续性的需求。
export const DefaultEventPriority: EventPriority = DefaultLane;

// IdleEventPriority: 空闲事件的优先级
// 空闲事件是指那些可以在浏览器空闲时处理的事件。
// 用于处理那些可以延迟执行的任务或事件,例如日志记录或非关键性更新。
export const IdleEventPriority: EventPriority = IdleLane;

6.位运算在优先级调度中的作用

我们可以将车道lane模型看作是管理多个优先级队列的一种机制,我们在调度时需要考虑,当前车道是否被占用,工作完如何释放、如何获取最高优先级车道【每次渲染选最高优先级的先调度】、合并赛道主要是用于批量更新,react有时会将多次更新合并。

占用赛道

我们回头看位运算中位掩码的作用

const READ = 1; // 0001 
const WRITE = 2; // 0010 
const EXECUTE = 4; // 0100 
let permissions = READ | WRITE; // 0011
  • 场景
    • 当某个任务加入时,需要占用车道
  • 运算过程
    • 运算方式:|
    • 运算结果:车道占用,标记为1

【react18原理探究实践】JS中的位运算&react中的lane模型

释放赛道

  • 场景
    • SyncLane 任务执行完,需要释放占用的赛道
  • 运算过程
    • 运算方式:a & ~b
    • 运算结果:车道释放
export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
  return set & ~subset;
}
const READ = 1; // 0001 
const WRITE = 2; // 0010 
const EXECUTE = 4; // 0100
// 移除写权限,
0111 & 1101 = 0101,这样写的位置就为0
permissions &= ~WRITE; // 0101

合并赛道

  • 场景

    • 更新任务中触发新的高优先级更新,获取批量更新车道合并
  • 运算过程

    • 运算方式:位或运算 - a | b
    • 运算结果:DefaultLane和SyncLane分别占用了第1条和第5条赛道
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a | b;
}
DefaultLane优先级为16SyncLane优先级为1

16 | 1 = 17

17的二进制值为10001
16的二进制值为100001的二进制值为00001

超出最高优先级赛道

  • 场景
    • 当前多个车道占用,进入调度后选取最高优先级车道任务调度
  • 运算过程
    • 运算方式:位与+符号位取反 - a & -b
    • 运算结果:最高优先级
export function getHighestPriorityLane(lanes: Lanes): Lane {
  return lanes & -lanes;
}

17 & -17 = 1

17的二进制值为10001
-17的二进制值为00001
1000100001进行位与运算得到1,也就是SyncLane

判断赛道是否被占用

  • 运算过程

    • 运算方式:位与 (lanes & SyncLane) !== NoLanes
    • 运算结果:0代表当前调度优先级高于某个Update对象优先级
运算公式
(1 & 16) == 16

1的二进制值为00001
16的二进制值为10000 
0000110000进行位与运算得到0
export function includesSyncLane(lanes: Lanes) {
  return (lanes & SyncLane) !== NoLanes;
}

推荐阅读

工程化系列

本系列是一个从0到1的实现过程,如果您有耐心跟着实现,您可以实现一个完整的react18 + ts5 + webpack5 + 代码质量&代码风格检测&自动修复 + storybook8 + rollup + git action实现的一个完整的组件库模板项目。如果您不打算自己配置,也可以直接clone组件库仓库切换到rollup_comp分支即是完整的项目,当前实现已经足够个人使用,后续我们会新增webpack5优化、按需加载组件、实现一些常见的组件封装:包括但不限于拖拽排序、瀑布流、穿梭框、弹窗等

【react18原理探究实践】JS中的位运算&react中的lane模型

面试手写系列

react实现原理系列

其他

🍋 写在最后

如果您看到这里了,并且觉得这篇文章对您有所帮助,希望您能够点赞👍和收藏⭐支持一下作者🙇🙇🙇,感谢🍺🍺!如果文中有任何不准确之处,也欢迎您指正,共同进步。感谢您的阅读,期待您的点赞👍和收藏⭐!

感兴趣的同学可以关注下我的公众号ObjectX前端实验室

🌟 少走弯路 | ObjectX前端实验室 🛠️「精选资源|实战经验|技术洞见」

转载自:https://juejin.cn/post/7380579033547178021
评论
请登录