React源码解析(四):位运算

448 阅读5分钟

前言

本篇是React源码解析系列第四篇。主要学习二进制相关概念以及位运算,作为基础知识补充,不作为源码内容。源码版本为v18.2.0。
上一篇我们讲到了 react-reconciler/src/ReactFiberWorkLoop.js,在这个文件中我们学习了React简化的初次渲染,最重要的beginWorkcompleteWorkcommitRoot等方法我们还未涉及。在此之前,我们需要先来了解如何进行位运算。

位运算

React在对节点进行标记(如更新、插入、删除等)、任务优先级标记等大量使用了位运算,相比于其他方式,位运算有效率高计算简单节省空间等诸多好处

比特(bit)

比特是二进制单位(binary unit)的缩写,是表示信息的最小单位,它只有01两种状态。在js中,声明一个二进制数需要在前面加0b字段。

// 2
const binaryNumber = 0b010;
// 查看二进制
binaryNumber.toString(2); // '10'

位运算符

React中用到的位运算符有三种: &(按位与)|(按位或)~(按位取反)

  • 按位与: 对比二进制数的每一位,如果都是1则为1,否则为0;
  • 按位或: 对比二进制数的每一位,如果有一个是1则为1,否则为0;
  • 按位取反: 对比二进制数的每一位,如果为0则取1,1则取0;

如以下几种计算:

// 伪代码
00001100 & 00001010 = 00001000;
00001100 | 00001010 = 00001110;
~00001100 = 11110011

第三种按位取反如果我们在控制台打印,如 ~ 4=-5,~-2 = 1,为什么会这样呢,这需要我们知道什么是原码、反码、补码;

原码、反码、补码

首先,因为计算机中只能存储0和1,是没有符号的存储的,因此计算机会以最高位表示符号。

原码

原码表示法很简单,第一位表示符号,后面几位表示值的绝对值。以8bit二进制数来说,如 00000100=4、10000100=-4,同时因为+0和-0其实都是0,因此1000000会被表示为-2^7,表示范围为[-2^7,2^7-1],即[-128,127]

但是思考一下如果我们对二进制使用加法,如 00000100(4) + 00000010(2) = 00000110(6) 是正确的,但是如果用正数加上负数则会出问题,如 00000100(4) + 10000010(-2) = 10000110(-6),完全是错误的。

反码

反码的存在是为了正确计算负数。
反码的表示方法是:正数的反码是其本身,和原码一样。负数的反码是原码的符号位不变,其余各位按位取反。
如 00000001=1、1111110=-1,比较特殊的是 00000000 表示0,那-0的反码则是 11111111。
因此再看到上面的思考,1+(-1)=0用反码则表示为 00000001(1) + 11111110(-1) = 11111111(-0);

补码

补码是为了简化运算,将减法变为加法而发明的数字表示法。
其规则是整数的补码和原码一样,负数的补码是其反码末尾加1。最高位溢出则舍弃。
如 00000001=1、11111111=-1, 00000001 + 11111111 = (1舍弃)00000000。

额外内容:如何按位取反后转为十进制

正整数补码取反之后符号位置为1,是一个负整数,所以再按照负整数计算补码的方式逆运算得到原码 逆运算得到原码,首先将取反的补码转成反码,公式:反码=补码 - 1,然后将反码转成原码,符号位不变,其他位取反

十进制  ---->    原码    ---->   反码    ---->   补码     ---->  补码取反   ---->  补码-1得到反码  ---->  转成原码
    1   ----> 00000001  ----> 0000001  ---->  00000001  ----> 11111110   ---->   11111101    ---->  10000010

负整数补码取反之后符号位置为0,是一个正整数,因正整数的反码与补码就是本身,所以不需要再进行逆运算

十进制  ---->    原码    ---->   反码     ---->   补码      ---->   补码取反得原码  
    -1  ---->  10000001  ----> 11111110  ---->  11111111   ---->     00000000  

使用位运算

看以下几个例子

const NoFlags = 0b00000000;// 0
const Placement = 0b00000001;// 1
const Update = 0b00000010;// 2

let flag = NoFlags;
// 增加
flag |= Placement;// 1
flag |= Update;// 3

// 是否包含
(flag & Placement) === Placement;
(flag & Update) === Update;
// 是否不包含
(flag & Placement) === NoFlags;
(flag & Update) === NoFlags;

//删除
flag &= ~Placement;// 2
flag &= ~Update;// 0

至此,我们便学会了位运算。

Bonus 按位异或

按位异或也是一种位运算符,它的计算方式是对比两个二进制数的每一位,如果相同则取0,不同则取1

101110
100100
------
001010

它具有以下特点

  • 任何数和0异或都为其本身, a⊕0=a
  • 任何数和自身异或都为0, a⊕a=0
  • 异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b

学会了这个操作,可以思考一下leetcode第136题:只出现一次的数字。

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

function singleNumber(nums) {
    let ans = 0;
    for (const num of nums) {
        ans ^= num;
    }
    return ans;
};

本文正在参加「金石计划」