世界上有10种人,一种是懂二进制的,另一种是不懂的。
我们应该在中学时期就学习过数制转换,其中二进制是接触最多的了吧。现在比较流行的前端框架,react、vue等的实现中,一些标志位或者状态的运算也使用了二进制,理解这些运算的目的,可以帮助我们更好的掌握框架原理。
可能有些小伙伴对于二进制的一些概念比较模糊了,下面就来先盘一盘~
原码、反码、补码
原码是一种计算机中对数字的二进制定点表示方法。
原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。
整数的符号位与数值位之间用“,”隔开,小数的符号位与数值位之间用“.”
优点:简单直观;例如,我们用8位二进制表示一个数,+11的原码为00001011,-11的原码就是10001011
缺点:不能直接参加运算,可能会出错。例如数学上,1+(-1)=0,而在二进制中00000001+10000001=10000010,换算成十进制为-2。显然出错了。
反码:通常是用来由原码求补码或者由补码求原码的过渡码
反码跟原码是正数时,一样;负数时,反码就是原码符号位除外,其他位按位取反
在计算机系统中,数值一律用补码来表示和存储。
原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理
正整数的补码是其二进制表示,与原码相同
负整数的补码,将其原码除符号位外的所有位取反(0变1,1变0,符号位为1不变)后加1,即反码加1
二进制逻辑运算
与 &
两个相“与”的逻辑变量中,只要有一个为0,结果就为0。 仅当两个变量都为1时,“与”运算的结果才为1。
或 |
两个相“或”的逻辑变量中,只要有一个为1,结果就为1。 仅当两个变量都为0时,或运算的结果才为0。
非 ~
逻辑变量为0时,“非”运算的结果为1。逻辑变量为1时,“非”运算的结果为0。
0000 0000 0000 0000 0000 0000 0000 0011 // 3
1111 1111 1111 1111 1111 1111 1111 1100 // ~3
// 上面是计算机存储的~3的补码,通过补码可以得到源码,即
// 减1 -> 1111 1111 1111 1111 1111 1111 1111 1011
// 除第一位符号位,其他按位取反 -> 1000 0000 0000 0000 0000 0000 0000 0100, 即十进制-4
异或 ^
两个相“异或”的逻辑运算变量取值相同时,“异或”的结果为0。取值相异时,“异或”的结果为1。
左移 <<
所有二进制位左移n位,右边补n个0,超出位数后左边的位丢弃
eg.0b0011 << 2 = 0b001100, 3左移两位变12,左移可以看做是乘以2的n次方
右移 >>
所有二进制位右移n位,正数左边补n个0,负数左边补n个1,右边的位丢弃
eg.0b0011 << 1 = 0b0001, 3右移一位变1,右移可以看做是除以2的n次方
js的模运算,如果不能整除的话,会得到一个浮点数; 比如,数组遍历,求中间位置的索引下标,除以2就可以用右移一位实现
二进制逻辑运算的应用
以最简单的情况举例,只看一个二进制位,1表示属性开,0表示关:
-
变量a赋予属性:a | 1
-
变量a删除属性:a & 0, 当然也就是 a & ~1
所以,变量a赋予/删除属性,就可以统一成跟二进制位1的运算
-
判断a是否具有属性:a & 1 === 1 或者 a & 1 !== 0
react中的二进制
js表示二进制以'0b'开头,例如用四位表示
const b1 = 0b0000 // 十进制 0
const b2 = 0b0001 // 十进制 1
react引入Lane,可以在expirationTime的基础上(ExpirationTime模型可能会导致低优先级任务长时间等待/饿死),更细粒度的控制优先级,具体的实现在packages\react-reconciler\src\ReactFiberLane.old.js中。
export type Lanes = number;
export type Lane = number;
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;
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;
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;
js在内存中是不区分具体类型的,只区分基本类型(null,undefined,number,boolean,string)和引用类型(Obeject,function,array)。
In JavaScript, Number is a numeric data type in the double-precision 64-bit floating point format (IEEE 754). In other programming languages different numeric types exist; for example, Integers, Floats, Doubles, or Bignums.
Number类型占用的存储空间是64位,也就是8个字节。
Bitwise operators treat their operands as a set of 32 bits (zeros and ones) and return standard JavaScript numerical values.
js位运算会将操作数转成32位有符号整数,范围是(-2^31 ~ 2^31-1)。所以,除了符号位,只有31位参与位运算,TotalLanes的值也就是为31。
判断属性
export function includesSyncLane(lanes: Lanes): boolean {
return (lanes & SyncLane) !== NoLanes;
}
SyncLane只有最后一位是1,NoLanes所有位都是0,这不就是上面二进制逻辑运算的第三条,判断是否具有属性么☺
观察下上面代码中对不同Lane的定义,也是只有某一位是1,其余位都是0,变量跟它们做&运算,再跟NoLane比较,就可以判断出是否具有这一属性。
赋予属性
const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a | b;
}
删除属性
export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
return set & ~subset;
}
这个操作从命名也能看出来哈,react中的方法命名真的是很友好ღ
其他方法无非也是上面的三个方法,加上左移,右移的组合,知道了这些运算套路之后,再去理解源码,就so easy啦~
下面的两个个方法看起来稍微有些不一样
export function getHighestPriorityLane(lanes: Lanes): Lane {
return lanes & -lanes;
}
function pickArbitraryLaneIndex(lanes: Lanes) {
return 31 - clz32(lanes);
}
// ...
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
nextLanes |= entanglements[index];
lanes &= ~lane;
}
上面讲补码的时候提到,数值在计算机中都是用补码存储的,那么-lanes的补码就是lanes按位取反再加1,结果就是所有是1的位中最低的那一位跟原来一样是1,其余位都跟原来相反;再做&运算,结果就是除了那一位是1,其他位都是0。
Lane越低位优先级越高,所以getHighestPriorityLane也就是获取最高优先级啦
而pickArbitraryLaneIndex相关方法正好相反,获取最低优先级。
- clz32(lanes)计算前导0的个数
- 31 - clz32(lanes)第一个1所在的位
- 1 << (31 - clz32(lanes))跟原来的数值相比,第一个1之后的位全部是0,这样就从原来的数值得到了最低优先级
还有对上下文的判断也使用了二进制运算,跟Lane的操作是一样的道理。
export const NoContext = /* */ 0b000;
const BatchedContext = /* */ 0b001;
export const RenderContext = /* */ 0b010;
export const CommitContext = /* */ 0b100;
看过了上面源码中的二进制运算,相信你对它的简洁高效也有了一定的认识。在实际开发中,如果有适用的场景,也可以尝试使用下哦~ 了解了这些操作,就可以去愉快的阅读源码啦~
参考