大家好,我是Kason。这两年,很多朋友向我抱怨React的源码,比如:调度器为什么要用一个小的顶堆数据结构,而不是直接用数组?源码中的各种单向链表和循环链表不能直接用数组吗?源码React中的各种位操作有必要吗?作为一个依赖业务的框架,为了提升一点点运行时的性能,React不惜将源码复杂化。在涉及状态、标志位和优先级操作的地方大量使用位操作。本文将对其中比较有代表性的一些进行讲解。学会后,遇到类似场景,一展身手,你就是业务线最美少年。JS中常用的几种位运算,位运算的操作数会转换为Int32(32位有符号整数),Int32对应位运算后的浮点数。在React中,主要有3种位运算符——按位与、按位或、按位非。按位与(&)对两个二进制操作数的每一位,如果都为1,则结果为1,否则为0。例如计算3&2,首先将操作数转换为Int32://3对应于Int320b0000000000000000000000000000011//2对应Int320b00000000000000000000000000000010。32位):00000011&00000010----------00000010,所以3&2转换为浮点数后计算结果为2。按位或(|)对两个二进制操作数的每一位,如果都为0,则结果为0,否则为1。计算10|3:00001010|00000011--------00001011转换为浮点数后计算结果为11。按位非(~)对二进制操作数的每一位进行逐位取反运算(0、1互换)。对于~3,把3转成Int32再逐位取反://3对应Int320b0000000000000000000000000000011//逐位取反0b11111111111111111111111111100转换成浮点数的计算结果为-4。如果你对这个结果有疑问,可以去了解一下互补的知识。下面我们由易到难,看看位运算在React中的应用。React源码内部有多个上下文,在执行一个函数的时候,往往需要判断自己当前处于哪个上下文中。假设有三个context://AcontextconstA=1;//BcontextconstB=2;//不在contextconstNoContext=0;进入上下文时,可以使用按位或运算标志进入://当前上下文letcurContext=0;//进入A上下文curContext|=A;我们还是以8位二进制为例(同样应该是Int32,这里为了简单起见),curContext和A进行按位或操作:00000000//curContext|00000001//A-----------00000001这时候可以结合按位与运算和NoContext来判断自己是否在某个上下文中://是否在A上下文中true(curContext&A)!==NoContext//是否在上下文B中?false(curContext&B)!==NoContext离开一个上下文后,结合按位AND和按位NOT来去掉标志://从当前上下文中移除上下文AcurContext&=~A;//是否在A上下文中false(curContext&A)!==NoContextcurContext和~A进行按位与运算:00000001//curContext&11111110//~A--------00000000isfromcurContextRemoveAfrom.当业务中需要同时处理多个状态时,可以使用上层操作等技巧。优先级计算在React中,调用this.setState触发的更新在不同情况下会有不同的优先级。优先级之间的比较和选择也使用位操作。具体来说,React使用31位来保存“update”(之所以是31而不是32是因为Int32的最高位是符号位,并没有保存具体的数字)。低位的更新优先级越高(需要越优先处理)。例如,假设当前应用程序中有2个更新:0b0000000000000000000000000010001。第一个更新优先级最高(需要同步处理),第五个是默认优先级。React经常需要找出哪个是当前最高优先级的更新(上例中的第一个),方法如下:functiongetHighestPriorityLane(lanes){returnlanes&-lanes;}说明,因为Int32使用“补码”表示,所以-lanes可以看成下面两步操作:为了直观起见,将lanes(~lanes)取反加1,用8位表示:lanes00010001~lanes11101110//第一步+111101111//第二步,lanes&-lanes如下:00010001//lanes&11101111//-lanes------------00000001是第一个(现有更新中优先级最高)。小结虽然位运算在业务中并不常用,但是在特定场景下进行位运算是一种方便高效的方式。这波操作你爱吗?
