JavaScript中的位操作符

整数的存储

在计算机中,所有的数据都是以二进制的形式储存的。在 c++ 中,整数储存的位数往往和编译器的实现有关,而在 ECMAScript 中的所有数值都是以 IEEE-754 64位的格式储存的,当然位操作符并不直接操作 64 位的值。而是先将 64 位转换为 32 位,然后执行操作,最后再将结果转换位 64 位。对于开发人员来说,整个过程就好像 64 位格式是透明的,只存在 32 位的整数一样。

学习过 c++ 语言基础的同学都知道,对于有符号的整数,32位中的前31位都用来表示数值,而第32位用来表示符号位:0 表示正数,1 表示负数,这个叫做符号位。对于正数,以纯二进制格式存储,而负数,则以二进制补码的格式。计算补码,则是将二进制的原码取反加 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

当然,最左边的第32位,在处理有符号整数时是不能访问的。

按位非(NOT)

按位非操作符在 JS 中由一个波浪线 ~ 表示,返回的结果就是每一位取反获得的反码。根据负数的规则,我们可以得出反码的本质:操作数的负值减 1。但由于位操作符在底层直接操作,因此速度更快。

var a = 25;  // 0000 0000 0000 0000 0000 0000 0001 1001
var b = ~25; // 1111 1111 1111 1111 1111 1111 1110 0110
console.log(b) // -26

按位与(AND)

按位与由一个 & 符号表示,它有两个操作符数,本质上就是每一位对齐然后对相同位置上的值进行 AND 操作。所谓 AND 操作,和逻辑上的 AND 类似:

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

举个例子:

var result = 25 & 3
console.log(result) // 1

//过程如下:
//  25 = 0000 0000 0000 0000 0000 0000 0001 1001
//   3 = 0000 0000 0000 0000 0000 0000 0000 0011
// ---------------------------------------------
// AND = 0000 0000 0000 0000 0000 0000 0000 0001
// 即结果为 1

按位或(OR)

按位或有 | 符号表示,同样也是两个操作数,对每一位进行类似逻辑上的或操作:

第一数值 第二个数值 结果
1 1 1
1 0 1
0 1 1
0 0 0
var result = 25 | 3
console.log(result) // 27

//过程如下:
//  25 = 0000 0000 0000 0000 0000 0000 0001 1001
//   3 = 0000 0000 0000 0000 0000 0000 0000 0011
// ---------------------------------------------
//  OR = 0000 0000 0000 0000 0000 0000 0001 1011
// 即结果为 27

按位异或(XOR)

按位异或(XOR)由 ^ 表示,也有两个操作数,按下面的表来操作:

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

按位异或和按位或不同的地方在于,这个操作要求两个数值的对应位上必须相异时才返回 1,如果都是 0 或者都是 1 则返回 0。

var result = 25 ^ 3
console.log(result) // 26

//过程如下:
//  25 = 0000 0000 0000 0000 0000 0000 0001 1001
//   3 = 0000 0000 0000 0000 0000 0000 0000 0011
// ---------------------------------------------
// XOR = 0000 0000 0000 0000 0000 0000 0001 1010
// 即结果为 26

左移

左移由 << 表示,这个操作将数值的所有位向左移动指定的位数,左移并不会影响符号位。

var result = 2 << 5
console.log(result) // 64

//过程如下:
//   2 = 0000 0000 0000 0000 0000 0000 0000 0010
// ---------------------------------------------
// <<5 = 0000 0000 0000 0000 0000 0000 0100 0000
//                                        |____|
//                                        以0填充
// 即结果为 64

有符号的右移

有符号的右移由操作符 >> 表示,有符号的右移与左移正好相反,即如果 64 右移 5位,结果为 2。同样,在操作时也会出现空位,只不过空位出现在原数值的左侧、符号位的右侧,并且会用符号位的值来填充所有的空位。

var result = 64 >> 5
console.log(result) // 2

//过程如下:
//  64 = 0000 0000 0000 0000 0000 0000 0100 0000
// ---------------------------------------------
// >>5 = 0000 0000 0000 0000 0000 0000 0000 0010
//        |____|
//       以0填充
// 即结果为 2

无符号的右移

无符号的右移由操作符 >>> 表示,这个操作会将所有的 32 位都往右移动,对正数来说,结果仍然和有符号的右移相同。但对负数来说,结果就不一样了,首先,无符号右移是以 0 来填充空位,而不像有符号的一样以符号位的值来填充,其次,无符号右移会把负数的二进制码当作正数的二进制码,而且,由于负数以其绝对值的补码表示,因此右移的结果会非常之大。

var result = -64 >>> 5
console.log(result) // 134217726

//过程如下:
//   64 = 0000 0000 0000 0000 0000 0000 0100 0000
//  -64 = 1111 1111 1111 1111 1111 1111 1100 0000
// ---------------------------------------------
// >>>5 = 0000 0111 1111 1111 1111 1111 1111 1110
//        |____|
//       以0填充
// 即结果为 134217726