任何一门合格的计算机语言,底层都无一例外是二进制的运算结构,半导体单晶硅决定了计算机的本质0和1。JS当然也是支持位运算的,位运算在JS中有两大用处:数学计算和状态码
JS中位运算的实现
- 按位与( AND)
- 语法:a & b
- 描述:对于每一个比特位,只有两个操作数相应的比特位都是1时,结果才为1,否则为0。
- 按位或(OR)
- 语法:a | b
- 描述:对于每一个比特位,当两个操作数相应的比特位至少有一个1时,结果为1,否则为0。
- 按位异或(XOR)
- 语法: a ^ b
- 描述:对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。
- 按位非(NOT)
- 语法:~ a
- 描述:反转操作数的比特位,即0变成1,1变成0。
- 语法:~ a
- 左移(Left shift)
- 语法:a << b
- 描述:将 a 的二进制形式向左移 b (< 32) 比特位,右边用0填充。
- tip:看做小数点移动,左移(小数点往右移),右移(小数点往左移)
- 有符号右移
- 语法:a >> b
- 描述:将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位。
- 无符号右移
- 语法:a >>> b
- 描述:将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。
应用场景
- 数学运算(判断奇偶数)
按位与
:因为奇数的二进制比特位最后一位永远是1,偶数永远是0,所以==x & 1==,奇数永远是1,偶数永远是0(知乎上方应杭被大佬怼的时候拿出来的梗) - 标志位与掩码
例如,有 4 个标志位:
==标志位 A:我们有 ant
标志位 B:我们有 bat
标志位 C:我们有 cat
标志位 D:我们有 duck==标志位通过位序列 DCBA 来表示。当一个位被置位 (set) 时,它的值为 1 。当被清除 (clear) 时,它的值为 0 。例如一个变量 flags 的二进制值为 0101:
var flags = 5; // 二进制 0101
这个值表示:
==标志位 A 是 true (我们有 ant);
标志位 B 是 false (我们没有 bat);
标志位 C 是 true (我们有 cat);
标志位 D 是 false (我们没有 duck);==
因为位运算是 32 位的, 0101 实际上是 00000000000000000000000000000101。因为前面多余的 0 没有任何意义,所以他们可以被忽略。
掩码 (bitmask) 是一个通过与/或来读取标志位的位序列。典型的定义每个标志位的原语掩码如下:
var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; //0100
var FLAG_D = 8; // 1000
新的掩码可以在以上掩码上使用逻辑运算创建。例如,掩码 1011 可以通过 FLAG_A、FLAG_B 和 FLAG_D 逻辑或得到:
var mask = FLAG_A | FLAG_B | FLAG_D; // 0001 | 0010 | 1000 => 1011
某个特定的位可以通过与掩码做逻辑与运算得到,通过与掩码的与运算可以去掉无关的位,得到特定的位。例如,掩码 0100 可以用来检查标志位 C 是否被置位:
// 如果我们有 cat
if (flags & FLAG_C) { // 0101 & 0100 => 0100 => true // do stuff }
一个有多个位被置位的掩码表达任一/或者的含义。例如,以下两个表达是等价的:
// 如果我们有 bat 或者 cat 至少一个
// (0101 & 0010) || (0101 & 0100) => 0000 || 0100 => true
if ((flags & FLAG_B) || (flags & FLAG_C)) { // do
stuff
}// 如果我们有 bat 或者 cat 至少一个
var mask = FLAG_B | FLAG_C; // 0010 | 0100 =>0110
if (flags & mask) {
// 0101 & 0110 => 0100 => true
// do stuff
}
可以通过与掩码做或运算设置标志位,掩码中为 1 的位可以设置对应的位。例如掩码 1100 可用来设置位 C 和 D:
// 我们有 cat 和 duck var mask = FLAG_C | FLAG_D; // 0100 | 1000 => 1100
flags |= mask; // 0101 | 1100 => 1101
可以通过与掩码做与运算清除标志位,掩码中为 0 的位可以设置对应的位。掩码可以通过对原语掩码做非运算得到。例如,掩码 1010 可以用来清除标志位 A 和 C :
// 我们没有 ant 也没有 cat var mask = ~(FLAG_A | FLAG_C); // ~0101 => 1010
flags &= mask; // 1101 & 1010 => 1000
如上的掩码同样可以通过 ~FLAG_A & ~FLAG_C 得到(德摩根定律):
// 我们没有 ant 也没有 cat
var mask = ~FLAG_A & ~FLAG_C;
flags &= mask;
// 1101 & 1010 => 1000
标志位可以使用异或运算切换。所有值为 1 的为可以切换对应的位。例如,掩码 0110 可以用来切换标志位 B 和 C:
// 如果我们以前没有 bat ,那么我们现在有 bat
// 但是如果我们已经有了一个,那么现在没有了
// 对 cat 也是相同的情况
var mask = FLAG_B | FLAG_C;
flags = flags ^ mask;
// 1100 ^ 0110 =>1010
最后,所有标志位可以通过非运算翻转:
// entering parallel universe…
flags = ~flags;
// ~1010 => 0101