前言
本篇是React源码解析系列第四篇。主要学习二进制相关概念以及位运算,作为基础知识补充,不作为源码内容。源码版本为v18.2.0。
上一篇我们讲到了 react-reconciler/src/ReactFiberWorkLoop.js
,在这个文件中我们学习了React简化的初次渲染,最重要的beginWork
、completeWork
、commitRoot
等方法我们还未涉及。在此之前,我们需要先来了解如何进行位运算。
位运算
React在对节点进行标记(如更新、插入、删除等)、任务优先级标记等大量使用了位运算,相比于其他方式,位运算有效率高
、计算简单
、节省空间
等诸多好处
比特(bit)
比特是二进制单位(binary unit)
的缩写,是表示信息的最小单位
,它只有0
和1
两种状态。在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;
};
本文正在参加「金石计划」