js的位操作

今天在看js的Array的方法时, 看到源码中有下面的一段代码, 挺有意思, 是关于位运算的, 就拿来研究了一下:

1
var len = t.length >>> 0;

这里使用了 >>> ,无符号右移运算符, 还右移了 0 位, 初看有点蒙, 到底啥意思? 带着疑问做了如下实验:

1
2
3
4
5
6
7
8
9
10
11
12
console.log(1 >>> 0);               //1
console.log(1.1 >>> 0); //1
console.log(NaN >>> 0); //0
console.log(true >>> 0); //1
console.log(false >>> 0); //0
console.log('a' >>> 0); //0
console.log('123' >>> 0); //123
console.log('a123' >>> 0); //0
console.log({} >>> 0); //0
console.log(function() {} >>> 0); //0
console.log(null >>> 0); //0
console.log(undefined >>> 0); //0

从结果来看,不管是什么类型的数据,都被强制转换成了整型。 为什么 >>> 运算符会如此呢?这要从js的位运算符说起。

位运算符

位运算符用于在最基本的层次上,即按内存中表示数值的位来操作数值。ECMAScript中的所有数值都以IEEE-745 64位格式存储,但 位操作符并不直接操作64位的值,而是将64位的值转换成32位的整数,然后执行操作,最后再将结果转换回64位。

对于有符号的整数,32位中的前31位表示整数的值,第32位表示整数的符号,0表示整数,1表示负数。这个表示符号的位叫做 符号位,符号位的值决定了其他位数值的格式。其中,整数以纯二进制格式存储,31位中的每一位都表示2的幂。负数同样以二进制存储,但使用的格式是二进制补码 计算一个数值的二进制补码,需要经过下列3个步骤: 1.求这个数值绝对值的二进制码 2.求二进制码的反码,即将0替换为1,1替换为0 3.得到的二进制反码加1

例如,要求-18的二进制码,首先求18的二进制码:

0000 0000 0000 0000 0000 0000 0001 0010

求其反码:

1111 1111 1111 1111 1111 1111 1110 1101

最后反码加1

1111 1111 1111 1111 1111 1111 1110 1110

这样就求得了-18的二进制表示。

如果对非数值应用位操作符,会显示用Number()函数将改数值转换成一个数值(自动完成),然后再用应位操作符。

按位非 ~

按位非的结果就是,返回数值的反码。

1
2
3
var num = 25;           //二进制 00000000000000000000000000011001
var num2 = ~num; //二进制 11111111111111111111111111100110
console.log(num2); //-26

这里,对25执行按位非操作,结果得到了-26.这也验证了按位非操作的实质:操作数的负值减1 。因此,下面的代码也能得到相同的结果:

1
2
3
var num = 25;
var num2 = -num - 1;
console.log(num2);

虽然以上代码也能返回同样的结果,但由于按位非是在数值表示的最底层执行操作,因此速度更快。

按位与 &

按位或 |

按位异或 ^

按位异或操作扶由一个插入符号(^)表示,也有两个操作数。以下是按位异或的真值表:

第一个数值的位 第二个数值的位 结果
1 1 0
1 0 1
0 1 1
0 0 0

左移 <<

将数值的所有位向左移动指定的位。 左移的结果是,移动n位,乘以2的n次幂。例如,2<<5 = 2*2^5 = 64

左移不会影响操作数的符号位。

有符号右移[算数右移] >>

符号位不移,其他位右移,在左端补k个最高有效位的值。

无符号右移[逻辑右移] >>>

这个操作符会将数值的所有32位都向右移动。对正数来说无符号右移的结果和有符号相同。但是对于负数来说,情况就不一样了。 无符号右移操作符会把负数的二进制码当成正数的二进制码,而且由于负数以其绝对值的二进制补码形式表示,因此会导致无符号右移后的结果非常之大。 例如,上面的例子中,-18的二进制码:

1111 1111 1111 1111 1111 1111 1110 1110

右移一位: -18 >>> 1

0111 1111 1111 1111 1111 1111 1111 0111

这时将这个二进制码转换成正数是:2147483639


好了,我们回过头来看最初在js源码中看到的关于>>>那个问题,任何类型的数值,经过无符号右移0位后都被强制转换成了整数。 因为,在右移之前,先使用Number()函数转换成数值类型,移动0位相当于没移,因此,无论何种类型的值,>>>0被强制转换成整数类型。 那么,问题来了,Number()进行转换的时候,有一些是不能转换为整数的,比如,Number(‘a’)得到的是NaN,NaN的类型也是number类型,但是,他却不是一个确定的值,js在对NaN进行位运算时,将0作为 运算元。

所以,如果看透了位运算,你会发现,其实 a>>>0 其实和 ~~a运算的结果是一样的,都是将其转换成整数的一个运算。