【react18原理探究实践】JS中的位运算&react中的lane模型
🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
🥑 你能学到什么?
希望在你阅读本篇文章之后,不会觉得浪费了时间。如果你跟着读下来,你将会学到:
- 原码、反码、补码
- JS中数值如何表示?
- 位运算规则&场景
- react的优先级调度机制
- 优先级转换
- lane车道模型
✍️系列文章react实现原理系列
一、原码、反码、补码
原码(Sign-Magnitude)
原码是一种直接表示整数的方法,其中最高位(最左边的位)表示符号,其余位表示数值的大小。对于 n 位的二进制数:
- 最高位为 0 表示正数。
- 最高位为 1 表示负数。 例如,对于 8 位表示法:
- +5 的原码表示为:
0000 0101
- -5 的原码表示为:
1000 0101
但是这种表示法有一个明显的缺点:存在两个 0,即 +0
(0000 0000
)和 -0
(1000 0000
)。
反码(One's Complement)
反码是一种在历史上曾使用过的表示方法,主要用于早期计算机。反码的表示规则:
- 正数的反码与原码相同。
- 负数的反码:将数值的绝对值按位取反(变 0 为 1,变 1 为 0)。 例如,对于 8 位表示法:
- +5 的反码表示为:
0000 0101
- -5 的反码表示为:
- 取绝对值的二进制表示:
0000 0101
- 按位取反:
1111 1010
反码也存在两个 0:+0
(0000 0000
)和-0
(1111 1111
)。反码在加减法运算时需要进行额外的处理,因此逐渐被补码取代。
- 取绝对值的二进制表示:
补码(Two's Complement)
补码是现代计算机中广泛使用的表示带符号整数的方法。它解决了原码的冗余 0 和加减运算的复杂性。补码的特点是只有一个 0(0000 0000
),并且负数的表示方式简化了计算。
补码的表示规则:
- 正数的补码与原码相同。
- 负数的补码:将数值的绝对值按位取反(变 0 为 1,变 1 为 0),然后加 1。 例如,对于 8 位表示法:
- +5 的补码表示为:
0000 0101
- -5 的补码表示为:
- 取绝对值的二进制表示:
0000 0101
- 按位取反:
1111 1010
- 加 1:
1111 1011
- 取绝对值的二进制表示:
补码表示法使得加减法运算更为简便。例如,5 + (-5)
直接按位相加,结果为 0。
二、补码的计算:5 + (-5)
直接按位相加如何计算
这里用8位表示
步骤 1:表示 5
和 -5
5
的二进制表示(8 位):0000 0101
-5
的补码表示:5
的二进制表示:0000 0101
- 按位取反:
1111 1010
- 加 1:
1111 1011
所以,-5
的补码表示为 1111 1011
。
步骤 2:按位相加 5
和 -5
满2进1
0000 0101 (5)
+ 1111 1011 (-5)
-------------
1 + 1 = 10
,结果是0
,进位1
0 + 1 + 1 = 10
,结果是0
,进位1
1 + 0 + 1 = 10
,结果是0
,进位1
0 + 1 + 1 = 10
,结果是0
,进位1
0 + 1 + 1 = 10
,结果是0
,进位1
0 + 1 + 1 = 10
,结果是0
,进位1
0 + 1 + 1 = 10
,结果是0
,进位1
0 + 1 = 1
,结果是1
这里需要注意最后超出8位的进位被忽略(在 8 位表示中)
0000 0000 (结果为 0)
三、JS中数值如何表示?
在 js 中,所有的数值都是以双精度浮点数(64 位)形式存储的,是由 IEEE 754 标准定制的。这种存储方式既适用于整数,也适用于小数。这与其他许多编程语言不同,它区分整数类型和浮点数类型。
IEEE 754 双精度浮点数
简单了解下即可,IEEE 754 双精度浮点数格式由 64 位组成,分为三个部分:
- 符号位(1 位)(S) :表示数值的正负。0 表示正数,1 表示负数。
- 指数(11 位)(E) :指数位,表示数值的规模(即大小),用偏移量表示,即实际的指数值要减去一个偏移量(Bias)。
- 尾数(52 位)(M) :表示数值的精度,隐含一个前导的 1,即 1.M。。
偏移量(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中的一些常见位运算:
位运算符&运算规则
-
按位与(AND) :
&
- 对应位都为1时,结果才为1。
- 示例:
5 & 3
(即0101 & 0011
)结果是0001
,即1
。
-
按位或(OR) :
|
- 对应位只要有一个为1,结果就是1。
- 示例:
5 | 3
(即0101 | 0011
)结果是0111
,即7
。
-
按位异或(XOR) :
^
- 对应位不同,结果为1;相同,结果为0。
- 示例:
5 ^ 3
(即0101 ^ 0011
)结果是0110
,即6
。
-
按位非(NOT) :
~
- 将数值的每一位取反,即0变1,1变0。
- 示例:
~5
(即~0101
)结果是1010
,即-6
(按补码表示)。
-
左移(Left Shift) :
<<
- 将数值的二进制位左移,右边补0。
- 示例:
5 << 1
(即0101 << 1
)结果是1010
,即10
。
-
右移(Signed Right Shift) :
>>
- 将数值的二进制位右移,左边补符号位(即正数补0,负数补1)。
- 示例:
5 >> 1
(即0101 >> 1
)结果是0010
,即2
。
-
无符号右移(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优先级做了分类和映射
2.scheduler
优先级
优先级级别 | 默认数值 | 版本大于'17.0.2'时的数值 | 使用情况 |
---|---|---|---|
ImmediatePriority | 99 | 1 | 用于需要立即执行的任务,如紧急更新 |
UserBlockingPriority | 98 | 2 | 用于需要快速响应用户交互的任务 |
NormalPriority | 97 | 3 | 用于大多数日常任务 |
LowPriority | 96 | 4 | 用于不需要紧急处理的任务 |
IdlePriority | 95 | 5 | 用于浏览器空闲时才执行的任务 |
NoPriority | 90 | 0 | 用于不指定优先级的任务 |
// 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个车道的名称、优先级和使用时机:
车道名称 | 优先级 | 使用时机 |
---|---|---|
SyncLane | 1 | 立即处理的任务,如用户点击和按键等离散事件。 |
InputContinuousHydrationLane | 2 | 持续输入事件的水合任务,如滚动和拖动。 |
InputContinuousLane | 3 | 持续输入事件,如滚动和拖动。 |
DefaultHydrationLane | 4 | 一般水合任务。 |
DefaultLane | 5 | 常规任务。 |
TransitionHydrationLane | 6 | 过渡任务的水合。 |
TransitionLane1 | 7 | 过渡任务1。 |
TransitionLane2 | 8 | 过渡任务2。 |
TransitionLane3 | 9 | 过渡任务3。 |
TransitionLane4 | 10 | 过渡任务4。 |
TransitionLane5 | 11 | 过渡任务5。 |
TransitionLane6 | 12 | 过渡任务6。 |
TransitionLane7 | 13 | 过渡任务7。 |
TransitionLane8 | 14 | 过渡任务8。 |
TransitionLane9 | 15 | 过渡任务9。 |
TransitionLane10 | 16 | 过渡任务10。 |
TransitionLane11 | 17 | 过渡任务11。 |
TransitionLane12 | 18 | 过渡任务12。 |
TransitionLane13 | 19 | 过渡任务13。 |
TransitionLane14 | 20 | 过渡任务14。 |
TransitionLane15 | 21 | 过渡任务15。 |
TransitionLane16 | 22 | 过渡任务16。 |
RetryLane1 | 23 | 需要重试的任务1。 |
RetryLane2 | 24 | 需要重试的任务2。 |
RetryLane3 | 25 | 需要重试的任务3。 |
RetryLane4 | 26 | 需要重试的任务4。 |
RetryLane5 | 27 | 需要重试的任务5。 |
SelectiveHydrationLane | 28 | 选择性水合任务。 |
IdleHydrationLane | 29 | 空闲时的水合任务。 |
IdleLane | 30 | 空闲时处理的任务。 |
OffscreenLane | 31 | 离屏任务,例如后台渲染。 |
- 高优先级车道(1-5):用于处理需要立即响应的任务。
- 中等优先级车道(6-27):用于处理过渡任务和需要重试的任务。
- 低优先级车道(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优先级
转换规则:对于非离散、连续的事件,会根据一定规则作转换.
事件优先级 | 处理的事件类型 |
---|---|
DiscreteEventPriority | cancel, click, close, ... |
ContinuousEventPriority | drag, dragenter, ... |
DefaultEventPriority | message |
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
- 运算方式:
释放赛道
- 场景
- 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优先级为16,SyncLane优先级为1
16 | 1 = 17
17的二进制值为10001
16的二进制值为10000,1的二进制值为00001
超出最高优先级赛道
- 场景
- 当前多个车道占用,进入调度后选取最高优先级车道任务调度
- 运算过程
- 运算方式:位与+符号位取反 -
a & -b
- 运算结果:最高优先级
- 运算方式:位与+符号位取反 -
export function getHighestPriorityLane(lanes: Lanes): Lane {
return lanes & -lanes;
}
17 & -17 = 1
17的二进制值为10001
-17的二进制值为00001
10001和00001进行位与运算得到1,也就是SyncLane
判断赛道是否被占用
-
运算过程
- 运算方式:位与
(lanes & SyncLane) !== NoLanes
- 运算结果:0代表当前调度优先级高于某个Update对象优先级
- 运算方式:位与
运算公式
(1 & 16) == 16
1的二进制值为00001
16的二进制值为10000
00001和10000进行位与运算得到0
export function includesSyncLane(lanes: Lanes) {
return (lanes & SyncLane) !== NoLanes;
}
推荐阅读
工程化系列
本系列是一个从0到1的实现过程,如果您有耐心跟着实现,您可以实现一个完整的react18 + ts5 + webpack5 + 代码质量&代码风格检测&自动修复 + storybook8 + rollup + git action
实现的一个完整的组件库模板项目。如果您不打算自己配置,也可以直接clone组件库仓库切换到rollup_comp
分支即是完整的项目,当前实现已经足够个人使用,后续我们会新增webpack5优化、按需加载组件、实现一些常见的组件封装:包括但不限于拖拽排序、瀑布流、穿梭框、弹窗等
面试手写系列
react实现原理系列
其他
🍋 写在最后
如果您看到这里了,并且觉得这篇文章对您有所帮助,希望您能够点赞👍和收藏⭐支持一下作者🙇🙇🙇,感谢🍺🍺!如果文中有任何不准确之处,也欢迎您指正,共同进步。感谢您的阅读,期待您的点赞👍和收藏⭐!
感兴趣的同学可以关注下我的公众号ObjectX前端实验室
🌟 少走弯路 | ObjectX前端实验室 🛠️「精选资源|实战经验|技术洞见」
转载自:https://juejin.cn/post/7380579033547178021