阅读React源码带来的按位运算符的思考

1,798 阅读6分钟

最近准备把react@17.x的源码认真看一遍, 在阅读的过程中发现源码中大量使用了按位运算符, 当初看《你不知道的Javascript》就曾看到这一章节, 当时没细看, 想着这玩意用的少, 没必要看。 果然, 天道好轮回, 欠下的迟早要还上, 这里就记录下js进制转换和按位运算符的那些事。

进制转换

大家都知道计算机都是使用的二进制码进行工作的,至于计算机为什么要使用二进制大家可以自行了解。而我们平时说的1, 2, 3 ...其实都指的是十进制,但是可能有些人还不清楚十进制怎么转换二进制, 八进制, 十六进制, 这里简单做个介绍。

我们常用的进制包括: 二进制, 八进制, 十进制, 十六进制。 它们之间区别在于数运算时是逢几进一位。比如二进制是逢2进一位,十进制也就是我们常用的0-9是逢10进一位。其他的同理。

十进制转换为二进制的方法

十进制数除2取余法,即十进制数除2,余数为权位上的数,得到的商值继续除,直到商为0为止。

这里从网上找了个图:

二进制转换为十进制的方法

把二进制数按权展开、相加即得十进制数。

其他的进制转换也是类似的, 这里不再做赘述。

ECMAScript整数

关于原码、 补码、 反码

之所以会有三种编码方式是因为计算机只会做加法运算, 如果只有原码, 正数和负数相加会出现问题, 于是有了反码, 但是原码和反码做运算会差1, 于是有了补码。

原码

计算机只能识别0和1,使用的是二进制。原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值,二进制位的首位标识该二进制数是一个正数还是负数,正数为0,负数为1。

简单理解就是二进制数的首位加一个辨别正负数的符号

反码
  1. 正数的反码等于原码;
  2. 负数的反码是在其原码的基础上,符号位(首位)不变,其余各个位取反
补码
  1. 正数的补码等于原码;
  2. 负数的补码是在其反码的基础上加1

ECMAScript 整数

ECMAScript 整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数)。在 ECMAScript 中,所有整数字面量默认都是有符号整数,这意味着什么呢?

有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数。数值范围从 -2147483648 到 2147483647。

可以以两种不同的方式存储二进制形式的有符号整数,一种用于存储正数,一种用于存储负数。正数是以真二进制形式存储的,前 31 位中的每一位都表示 2 的幂,从第 1 位(位 0)开始,表示 20,第 2 位(位 1)表示 21。没用到的位用 0 填充,即忽略不计。

负数也存储为二进制代码,不过采用的形式是二进制补码。计算数字二进制补码的步骤有三步:

  1. 确定该数字的非负版本的二进制表示(例如,要计算 -18的二进制补码,首先要确定 18 的二进制表示)
  2. 求得二进制反码,即要把 0 替换为 1,把 1 替换为 0
  3. 在二进制反码上加 1

有趣的是,把负整数转换成二进制字符串后,ECMAScript 并不以二进制补码的形式显示,而是用数字绝对值的标准二进制代码前面加负号的形式输出。

18的二进制:
10010

-18的二进制:
1111 1111 1111 1111 1111 1111 1110 1110

ECMAScript表示-18的二进制:
-10010

js进行进制转换的方法

  1. 十进制转换成其他进制
// 转二进制
var number = 12345
number.toString(2) //"11000000111001"
// 转八进制
number.toString(8) //"30071"
// 转十六进制
number.toString(16) //"3039"
  1. 其他进制转换成十进制
// 二进制转十进制
var number = 10110
parseInt(number, 2) //22
// 八进制转十进制
parseInt(number, 8) //4168
// 十六进制转十进制
parseInt(number, 16) //65808

位运算符

位运算就是对二进制数执行计算,是整数的逐位运算。

&运算符

&”运算符(位与)用于对两个二进制操作数逐位进行比较:

12 & 5			//4
=>
1100 & 101
=>
100
=> 
4

|运算符

|运算符(位或)用于对两个二进制操作数逐位进行比较:

12 | 5			//13
=>
1100 | 101
=>
1101
=> 
13

^运算符

“^”运算符(位异或)用于对两个二进制操作数逐位进行比较:

12 ^ 5			//9
=>
1100 | 101
=>
1001
=> 
9

~运算符

“~”运算符(位非)用于对一个二进制操作数逐位进行取反操作。

  1. 把运算数转换为 32 位的二进制整数。
  2. 逐位进行取反操作。
  3. 把二进制反码转换为十进制浮点数。

位非运算实际上就是对数字进行取负运算,再减 1。

~num === -num - 1

>>运算符

零填充左位移。通过从右推入零向左位移,并使最左边的位脱落

5 << 1	 //10	
=>
0101 << 1	
=>
1010
=> 
10

<<运算符

有符号右位移。通过从左推入最左位的拷贝来向右位移,并使最右边的位脱落。

5 >> 1	  //2	
=>
0101 >> 1	
=>
0010
=>
2

>>>运算符

零填充右位移。 通过从左推入零来向右位移,并使最右边的位脱落。

5 >>> 1	  //2	
=>
0101 >>> 1	
=>
0010
=>
2

一些简写

|=

a |= b 等价于 a = a | b

&=

a &= b 等价于 a = a & b

ps: 我就是看到React源码里有很多这玩意才想起来位运算符的。

后话

关于进制转换和位运算符到这里也有了个基础的了解, 后续我要搞明白为什么React源码中会大量使用这种按位运算符, 这样用的好处是什么。