详细聊聊React源码中的位运算技巧

编辑: admin 分类: javascript 发布时间: 2021-11-17 来源:互联网
目录
  • 前言
  • 几个常用位运算
    • 按位与(&)
    • 按位或(|)
    • 按位非(~)
  • 标记状态
    • 优先级计算
      • 总结

        前言

        这两年有不少朋友和我吐槽React源码,比如:

        • 调度器为什么用小顶堆这种数据结构,直接用数组不行?
        • 源码里各种单向链表、环状链表,直接用数组不行?
        • 源码里各种位运算,有必要么?

        作为业务依赖的框架,为了提升一点点运行时性能,React从不吝惜将源码写的很复杂。

        在涉及状态、标记位、优先级操作的地方大量使用了位运算。
        本文会讲解其中比较有代表性的部分。学到之后,当遇到类似场景时露一手,你就是业务线最靓的仔。

        几个常用位运算

        在JS中,位运算的操作数会先转换为Int32(32位有符号整型),执行完位运算会Int32对应浮点数。

        在React中,主要用到3种位运算符 —— 按位与、按位或、按位非。

        按位与(&)

        对于两个二进制操作数的每个bit,如果都为1,则结果为1,否则为0。

        举个例子,计算3 & 2,首先将操作数转化为Int32:

        // 3对应的 Int32
        0b000 0000 0000 0000 0000 0000 0000 0011 
        // 2对应的 Int32
        0b000 0000 0000 0000 0000 0000 0000 0010 
        

        为了直观,我们排除前面的0,只保留最后8位(实际参与计算的应该是32位):

          0000 0011
        & 0000 0010
        -----------
          0000 0010
        

        所以3 & 2计算结果转化为浮点数后为2。

        按位或(|)

        对于两个二进制操作数的每个bit,如果都为0,则结果为0,否则为1。

        计算10 | 3:

          0000 1010
        | 0000 0011
        -----------
          0000 1011
        

        计算结果转化为浮点数后为11。

        按位非(~)

        对一个二进制操作数的每个bit,逐位进行取反操作(0、1互换)

        对于~3,将3转化为Int32后逐位取反:

        // 3对应的 Int32
        0b000 0000 0000 0000 0000 0000 0000 0011 
        // 逐位取反
        0b111 1111 1111 1111 1111 1111 1111 1100
        

        计算结果转化为浮点数后为-4。cc防御http://www.558idc.com/gfip.html

        如果你对这个结果有疑惑,可以去了解补码相关知识

        让我们从易到难,看看位运算在React中的应用。

        标记状态

        React源码内部有多个上下文环境,在执行函数时经常需要判断当前处在哪个上下文环境中。

        假设共有三种上下文情况:

        // A上下文
        const A = 1;
        // B上下文
        const B = 2;
        // 没有处在上下文
        const NoContext = 0;
        

        当进入某个上下文时,可以使用按位或操作标记进入:

        // 当前所处上下文
        let curContext = 0;
        
        // 进入A上下文
        curContext |= A;
        

        我们用8位二进制举例(同样,实际应该是Int32,这里是为了简化),curContext与A执行按位或操作:

          0000 0000  // curContext
        | 0000 0001  // A
        -----------
          0000 0001
        

        此时可以结合按位与操作与NoContext来判断是否处在某一上下文中:

        // 是否处在A上下文中 true
        (curContext & A) !== NoContext
        
        // 是否处在B上下文中 false
        (curContext & B) !== NoContext
        

        离开某上下文后,结合按位与、按位非移除标记:

        // 从当前上下文中移除上下文A
        curContext &= ~A;
        
        // 是否处在A上下文中 false
        (curContext & A) !== NoContext
        

        curContext与~A执行按位与操作:

          0000 0001  // curContext
        & 1111 1110  // ~A
        -----------
          0000 0000
        

        即从curContext中移除A。

        当业务中需要同时处理多个状态时,可以使用如上位运算技巧。

        优先级计算

        在React中,不同情况下调用this.setState触发的更新会拥有不同优先级。优先级之间的比较、挑选同样使用了位运算。
        具体来说,React中用31个bit位保存更新(之所以是31而不是32是因为Int32的最高位是符号位,不保存具体的数)。

        处在越低bit位的更新优先级越高(越需要优先处理)。

        举个例子,假设当前应用存在2个更新:

        0b000 0000 0000 0000 0000 0000 0001 0001
        

        其中第1位的更新优先级最高(需要同步处理),第5位为默认优先级。

        React经常需要找出当前最高优先级的更新在哪一位(如上例子中在第一位),方法如下:

        function getHighestPriorityLane(lanes) {
          return lanes & -lanes;
        }
        

        解释下,由于Int32采用补码表示,所以-lanes可以看作如下两步操作:

        1. lanes取反(~lanes)
        2. 加1

        为了直观,用8位表示:

        lanes  0001 0001
        ~lanes 1110 1110 // 第一步
        +1     1110 1111 // 第二步
        

        则lanes & -lanes如下:

          0001 0001 // lanes  
        & 1110 1111 // -lanes
        -----------
          0000 0001
        

        取到的就是第一位(已有更新中最高的优先级)。

        总结

        虽然业务中不常使用位操作,但在特定场景下位操作时很方便、高效的方式。

        这波操作你爱了么?

        到此这篇关于React源码中位运算技巧的文章就介绍到这了,更多相关React源码中位运算技巧内容请搜索hwidc以前的文章或继续浏览下面的相关文章希望大家以后多多支持hwidc!