最近准备把react@17.x的源码认真看一遍, 在阅读的过程中发现源码中大量使用了按位运算符, 当初看《你不知道的Javascript》就曾看到这一章节, 当时没细看, 想着这玩意用的少, 没必要看。 果然, 天道好轮回, 欠下的迟早要还上, 这里就记录下js进制转换和按位运算符的那些事。
进制转换
大家都知道计算机都是使用的二进制码进行工作的,至于计算机为什么要使用二进制大家可以自行了解。而我们平时说的1, 2, 3 ...其实都指的是十进制,但是可能有些人还不清楚十进制怎么转换二进制, 八进制, 十六进制, 这里简单做个介绍。
我们常用的进制包括: 二进制, 八进制, 十进制, 十六进制。 它们之间区别在于数运算时是逢几进一位。比如二进制是逢2进一位,十进制也就是我们常用的0-9是逢10进一位。其他的同理。
十进制转换为二进制的方法
十进制数除2取余法,即十进制数除2,余数为权位上的数,得到的商值继续除,直到商为0为止。
这里从网上找了个图:
二进制转换为十进制的方法
把二进制数按权展开、相加即得十进制数。
其他的进制转换也是类似的, 这里不再做赘述。
ECMAScript整数
关于原码、 补码、 反码
之所以会有三种编码方式是因为计算机只会做加法运算, 如果只有原码, 正数和负数相加会出现问题, 于是有了反码, 但是原码和反码做运算会差1, 于是有了补码。
原码
计算机只能识别0和1,使用的是二进制。原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值,二进制位的首位标识该二进制数是一个正数还是负数,正数为0,负数为1。
简单理解就是二进制数的首位加一个辨别正负数的符号。
反码
- 正数的反码等于原码;
- 负数的反码是在其原码的基础上,符号位(首位)不变,其余各个位取反
补码
- 正数的补码等于原码;
- 负数的补码是在其反码的基础上加1
ECMAScript 整数
ECMAScript 整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数)。在 ECMAScript 中,所有整数字面量默认都是有符号整数,这意味着什么呢?
有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数。数值范围从 -2147483648 到 2147483647。
可以以两种不同的方式存储二进制形式的有符号整数,一种用于存储正数,一种用于存储负数。正数是以真二进制形式存储的,前 31 位中的每一位都表示 2 的幂,从第 1 位(位 0)开始,表示 20,第 2 位(位 1)表示 21。没用到的位用 0 填充,即忽略不计。
负数也存储为二进制代码,不过采用的形式是二进制补码。计算数字二进制补码的步骤有三步:
- 确定该数字的非负版本的二进制表示(例如,要计算 -18的二进制补码,首先要确定 18 的二进制表示)
- 求得二进制反码,即要把 0 替换为 1,把 1 替换为 0
- 在二进制反码上加 1
有趣的是,把负整数转换成二进制字符串后,ECMAScript 并不以二进制补码的形式显示,而是用数字绝对值的标准二进制代码前面加负号的形式输出。
18的二进制:
10010
-18的二进制:
1111 1111 1111 1111 1111 1111 1110 1110
ECMAScript表示-18的二进制:
-10010
js进行进制转换的方法
- 十进制转换成其他进制
// 转二进制
var number = 12345
number.toString(2) //"11000000111001"
// 转八进制
number.toString(8) //"30071"
// 转十六进制
number.toString(16) //"3039"
- 其他进制转换成十进制
// 二进制转十进制
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
~运算符
“~”运算符(位非)用于对一个二进制操作数逐位进行取反操作。
- 把运算数转换为 32 位的二进制整数。
- 逐位进行取反操作。
- 把二进制反码转换为十进制浮点数。
位非运算实际上就是对数字进行取负运算,再减 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源码中会大量使用这种按位运算符, 这样用的好处是什么。