看 react
源码过程中,发现这样的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const NoContext = 0b000000 ;const BatchedContext = 0b000001 ;const EventContext = 0b000010 ;const DiscreteEventContext = 0b000100 ;const LegacyUnbatchedContext = 0b001000 ;const RenderContext = 0b010000 ;const CommitContext = 0b100000 ;let executionContext = NoContext;executionContext &= ~BatchedContext; executionContext |= LegacyUnbatchedContext; if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { return msToExpirationTime(now()); }
~
、|
和 &
都是「位运算操作符」,平常写代码很少会用到,导致完全不理解这些运算符有什么用,react
源码中这些代码到底是什么意思呢?
~ 运算符 首先来看第一个运算符 ~
,它是所谓的「否定号」或者说「逻辑否」。
与之相对的就是「逻辑与」&
和「逻辑或」 |
。
它的计算过程很简单,就是「二进制取反」,即写出数字的二进制表示,每位都和之前相反,1 变成 0,0 变成 1。
它实质上是对数字求负,然后减1。 所以 ~1 === -2
、~0 === -1
这个很好理解,并且我们能得出 -2 的二进制表示正是 111110
& 运算符 这个相比 ~
运算符在计算过程上很好理解,但没有 ~
运算符好计算。
1 2 3 25 & 6 ; 25 & 7 ; 25 & 8 ;
上面代码的计算过程是这样的,把数字写成二进制,上下比较,如果都是 1 那么结果的相同位也是 1 否则就是 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0000 0000 0000 0000 0000 0000 0001 1001 0000 0000 0000 0000 0000 0000 0000 0110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1001 0000 0000 0000 0000 0000 0000 0000 0111 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0001 1001 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 1000
|
运算符与 &
刚好相反,二进制表示时,上下其中任意一个是 1 那么结果的相同位上也是 1。所以之前的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0000 0000 0000 0000 0000 0000 0001 1001 0000 0000 0000 0000 0000 0000 0000 0110 0000 0000 0000 0000 0000 0000 0001 1111 0000 0000 0000 0000 0000 0000 0001 1001 0000 0000 0000 0000 0000 0000 0000 0111 0000 0000 0000 0000 0000 0000 0001 1111 0000 0000 0000 0000 0000 0000 0001 1001 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0001 1001
和 &
一样无法直观地看出结果。
react 中的位运算 在了解三个运算符后,我们回过头看看 react
源码,它有多种用法
1 2 3 4 5 6 let executionContext = NoContext;executionContext &= ~BatchedContext; executionContext &= BatchedContext; executionContext |= BatchedContext; (executionContext & (RenderContext | CommitContext) === NoContext;
这里每个常量的值都是有目的的,分别为 0、1、2、4、8、16、32,而不是像枚举那样 0、1、2、3,就是为了配合位运算。
1 2 3 4 5 6 7 const NoContext = 0b000000 ; const BatchedContext = 0b000001 ; const EventContext = 0b000010 ; const DiscreteEventContext = 0b000100 ; const LegacyUnbatchedContext = 0b001000 ; const RenderContext = 0b010000 ; const CommitContext = 0b100000 ;
那么这些特定的数字是怎么配合位运算做到「有含义」的呢? **其实就是 executionContext
所对应二进制中 1 的位置,记录了它所「包含」的 xxxContext
**,举例来说 000011
表示 BatchedContext
和 EventContext
;110001
表示 CommitContext
和 RenderContext
和 BatchedContext
;
表示状态累加的 |
首先来看看 |
运算符,由于每个常量二进制的 1 都在不同位置,两个任意常量经过 |
运算符后,其实就是增加了 1 的位置
1 2 NoContext | BatchedContext; BatchedContext | RenderContext;
所以可以理解 |
运算符就是「记录新增 Context」或者说「状态的累加」。如果用传统的写法,可能是这样的
1 2 3 4 5 const BatchedContext = 'BatchedContext' ;const EventContext = 'EventContext' ;const executionContext = [];executionContext.push(EventContext);
判断状态是否存在的 &
然后是 &
运算符,同样在特定数字下它能表达特殊的含义。首先由于每个常量的 1 都在不同位置,所以两个常量直接 &
操作后结果肯定是 000000,但如果是经过 |
运算后的 executionContext
和常量进行 &
运算,结果就有意思了
1 2 3 4 5 6 7 8 let executionContext = NoContext;executionContext |= BatchedContext; executionContext &= BatchedContext; executionContext |= EventContext; executionContext &= BatchedContext; executionContext &= EventContext;
可以发现它有「判断是否存在」的含义,但由于使用了 &=
所以更确切的含义是「如果存在就覆盖」。
移除状态的 ~
这个符号目前看到的场景是和 &
一起使用
1 2 let executionContext = NoContext;executionContext &= ~BatchedContext;
前面说过 &=
是判断存在就覆盖的含义,但是 BatchedContext
多了个 ~
符号,含义肯定有所改变,那这段代码究竟是什么含义呢?
1 2 3 4 executionContext = 000000 & ~000001 ; executionContext = 000000 & 111110 ; executionContext = 000000 ;
换个用例
1 2 3 4 5 executionContext = NoContext; executionContext |= BatchedContext; executionContext |= EventContext; executionContext &= ~BatchedContext;
等同于
1 2 3 executionContext = 000011 & ~000001 ; executionContext = 000011 & 111110 ; executionContext = 000010 ;
它表达的是「移除指定状态」,如果用传统的写法
1 2 3 4 5 6 7 const BatchedContext = 'BatchedContext' ;const EventContext = 'EventContext' ;const executionContext = [];executionContext.push(EventContext); executionContext.filter((context ) => context !== BatchedContext);
位运算的优点 为什么 react
使用这么难理解的方式去实现这些逻辑呢,大概是因为位运算的性能好很多吧,如果使用数组来保存状态,无论是要占用堆内存,还是在判断时还需要在内存中查询,任何方面,相比位运算都没有一点优势,那么采用位运算看来是必然的选择了。
参考