任何一门合格的计算机语言,底层都无一例外是二进制的运算结构,半导体单晶硅决定了计算机的本质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。
  • 左移(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