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

目录
  • 前言
  • 几个常用位运算
    • 按位与(&)
    • 按位或(|)
    • 按位非(~)
  • 标记状态
  • 优先级计算
  • 总结

前言

这两年有不少朋友和我吐槽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。

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

让我们从易到难,看看位运算在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源码中位运算技巧内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入研究React中setState源码

    React作为一门前端框架,虽然只是focus在MVVM中的View部分,但还是实现了View和model的绑定.修改数据的同时,可以实现View的刷新.这大大简化了我们的逻辑,只用关心数据流的变化,同时减少了代码量,使得后期维护也更加方便.这个特性则要归功于setState()方法.React中利用队列机制来管理state,避免了很多重复的View刷新.下面我们来从源码角度探寻下setState机制. 1 还是先声明一个组件,从最开始一步步来寻源: class App extends Comp

  • React 源码中的依赖注入方法

    一.前言 依赖注入(Dependency Injection)这个概念的兴起已经有很长时间了,把这个概念融入到框架中达到出神入化境地的,非Spring莫属.然而在前端领域,似乎很少会提到这个概念,难道前端的代码就不需要解耦吗?前端的代码就没有依赖了?本文将以 React 的源码为例子,看看它是如何使用依赖注入这一设计模式的. 二.依赖注入的基本概念 在看代码之前,有必要先简单介绍一下依赖注入的基本概念.依赖注入和控制反转(Inversion of Control),这两个词经常一起出现.一句话表

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

    目录 前言 几个常用位运算 按位与(&) 按位或(|) 按位非(-) 标记状态 优先级计算 总结 前言 这两年有不少朋友和我吐槽React源码,比如: 调度器为什么用小顶堆这种数据结构,直接用数组不行? 源码里各种单向链表.环状链表,直接用数组不行? 源码里各种位运算,有必要么? 作为业务依赖的框架,为了提升一点点运行时性能,React从不吝惜将源码写的很复杂. 在涉及状态.标记位.优先级操作的地方大量使用了位运算. 本文会讲解其中比较有代表性的部分.学到之后,当遇到类似场景时露一手,你就是业务

  • HashMap源码中的位运算符&详解

    引言 最近在读HashMap源码的时候,发现在很多运算符替代常规运算符的现象.比如说用hash & (table.length-1) 来替代取模运算hash&(table.length):用if((e.hash & oldCap) == 0)判断扩容后元素的位置等等. 1.取模运算符%底层原理 ​总所周知,位运算&直接对二进制进行运算:而对于取模运算符%:a % b 相当于 a - a / b * b,底层实际上是除法器,究其根源也是由底层的减法和加法共同完成.所以其运行效

  • 深入分析React源码中的合成事件

    目录 热身准备 明确几个概念 事件系统角色划分 事件注册 registerSimpleEvents registerEvents$2 registerEvents$1 registerEvents$3 registerEvents 事件监听 事件派发 合成事件 捕获和冒泡 总结 热身准备 明确几个概念 在React@17.0.3版本中: 所有事件都是委托在id = root的DOM元素中(网上很多说是在document中,17版本不是了): 在应用中所有节点的事件监听其实都是在id = root

  • react 源码中位运算符的使用详解

    位运算符基本使用 按位与(&):a & b对于每一个比特位,两个操作数都为 1 时, 结果为 1, 否则为 0 按位或(|):a | b对于每一个比特位,两个操作数都为 0 时, 结果为 0, 否则为 1 按位异或(^):a ^ b对于每一个比特位,两个操作数相同时, 结果为 1, 否则为 0 按位非(~):~ a反转操作数的比特位, 即 0 变成 1, 1 变成 0 0000 0000 0000 0000 0000 0000 0000 0011 -> 3 1111 1111 111

  • 简单了解python的一些位运算技巧

    前言 位运算的性能大家想必是清楚的,效率绝对高.相信爱好源码的同学,在学习阅读源码的过程中会发现不少源码使用了位运算.但是为啥在实际编程过程中应用少呢?想必最大的原因,是较为难懂.不过,在面试的过程中,在手写代码过程中,写出一两个位运算的代码,还会让面试官眼前一亮的. 位运算常用的运算符包括&(按位与), | (按位或),~(按位非),^(按位异或),<< (有符号左移位) ,>>(有符号右移位). 下面用几个例子说明其应用,希望对你有所启发. 1.判断奇数还是偶数 通常判

  • react源码层分析协调与调度

    目录 requestEventTime requestUpdateLane findUpdateLane lanePriority LanePriority createUpdate enqueueUpdate 总结 协调与调度 reconciler流程 同步任务类型执行机制 异步任务类型执行机制 shouldYield performUnitOfWork beginWork completeUnitOfWork scheduler流程 performWorkUntilDeadline 总结 r

  • JDK源码中一些实用的“小技巧”总结

    前言 这段时间比较闲,就看起了jdk源码.一般的一个高级开发工程师, 能阅读一些源码对自己的提升还是蛮大的.本文总结了一些JDK源码中的"小技巧",分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 1 i++ vs i-- String源码的第985行,equals方法中 while (n--!= 0) { if (v1[i] != v2[i]) return false; i++; } 这段代码是用于判断字符串是否相等,但有个奇怪地方是用了i--!=0来做判断,我们通

  • React 源码调试方式

    目录 正文 断点调试 搜索定位 Chrome Devtools 调试 sourcemap npm 下载react包 插件注释 调试 React 最初源码 关联 react 源码项目 总结 正文 什么?调试 React 源码还有优雅和不优雅之分? 别着急,我们先来听个故事: 东东是一名前端工程师,主要用 React 技术栈,用了多年之后想深入一下,所以最近开始看 React 源码. 断点调试 他把 react 和 react-dom 包下载了下来,在项目里引入,开发服务跑起来后,打开 Chrome

  • react源码层探究setState作用

    目录 前言 为什么setState看起来是异步的 从first paint开始 触发组件更新 更新渲染fiber tree 写在最后 前言 在深究 React 的 setState 原理的时候,我们先要考虑一个问题:setState 是异步的吗? 首先以 class component 为例,请看下述代码(demo-0) class App extends React.Component { state = { count: 0 } handleCountClick = () => { this

随机推荐