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

时间:?2021-10-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。

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

让我们从易到难,看看位运算在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

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

总结

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

这波操作你爱了么?

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

链接: http://www.pannellisolari.net/article/detial/10723

为什么 Vue 中 template 有且只能一个 root ?

在前段时间看到这样一个关于 Vue 的问题,为什么每个组件 template 中有且只能一个 root?可能,大家在平常开发中,用的较多就是 template 写 html 的形式。当然,不排除用 JSX 和 render() 函数的。但是,究其本质,它们最终都会转化成 render() 函数

React源码解析之ExpirationTime

在React中,为防止某个update因为优先级的原因一直被打断而未能执行。React会设置一个ExpirationTime,当时间到了ExpirationTime的时候,如果某个update还未执行的话,React将会强制执行该update,这就是ExpirationTime的作用。

Vue源码中的JS骚操作

本文不准备解析Vue源码的运行原理,仅单纯探寻vue中工具函数中那些值得学习的骚操作 ,当前环境的一系列判断;user Agent常量的一系列操作

React源码解析之ReactDOM.render()

React更新的方式有三种:(1)ReactDOM.render() || hydrate(ReactDOMServer渲染)(2)setState(3)forceUpdate;接下来,我们就来看下ReactDOM.render()源码

vue源码中的检测方法

判断是否为undefined或null; 判断是否为Promise 函数;判断是否为简单数据类型;严格检查复杂数据类型;将驼峰字符串转成连接符 magicEightTall 转换成 magic-eight-tall

React useEffect的源码解读

对源码的解读有利于搞清楚Hooks到底做了什么,如果您觉得useEffect很“魔法”,这篇文章也许对您有些帮助。本篇博客篇幅有限,只看useEffect,力求简单明了,带您到React Hooks的深处看看

精读Hooks 取数-swr源码

取数是旺球体育在线业务的重要部分,也经历过几次演化:fetch 的兼容性已经足够好,足以替换包括 $.post 在内的各种取数封装。原生用得久了,发现拓展性更好,支持 ssr 的同构取数方案也挺好

Tina.js源码分析

tinaJs 是一款轻巧的渐进式微信小程序框架,不仅能充分利用原生小程序的能力,还易于调试。这个框架主要是对 Component,Page 两个全局方法进行了封装,本文主要介绍 [tinaJS 1.0.0]() 的 Paeg.define 内部做了些什么

哪些方面可以看出企业建站源码质量?

源代码是构成网站的核心,即网站程序代码,包括网站文件及目录结构,拥有源代码才拥有网站的全部。传统自助建站因其SAAS模式无法开放源代码,用户实质上是每年支付租金租用放在平台网站的使用权

JS源码解析之Array.prototype.sort

给一个 sort 的比较函数中返回0,表示当前比较的两个元素相等,照理说, sort(()=>0) 后数组的元素顺序是不变的,和我的测试效果一致,那为什么在 低版本的 chrome 上,不同长度的数组运用 sort(()=>0) 后效果不一样呢?

点击更多...

内容以共享,参考,研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!

Baidu