16-位运算符
5.9.1 位运算符
位运算符允许在数字的每个二进制位上执行操作。如果读者没有类似C语言这样偏底层的编程语言经验,或者对计算机内部如何存储数字不太了解,可能需要先了解相关的知识(可以跳过这部分内容,因为很少有应用使用到位运算符)。位运算符将其操作数当成二进制补码格式的32位有符号整型数字。因为在JavaScript中,所有的数字都是双精度的,JavaScript在执行位运算前会先将数字转换成32位的整型,并在返回结果之前转换回来。
位运算符与逻辑运算符的共同点在于它们也是执行逻辑操作(与、或、非、异或),不同的是它们在整型的每一个二进制位上进行运算。如表5-7所示,它们还包含了能将二进制位进行位移的位移运算符。
| 运 算 符 | 描 述 | 示 例 | | :----- | :----- | :----- | :----- | :----- | | & | 位与 | 0b1010 & 0b1100 // 结果: 0b1000 | | | | 位或 | 0b1010 | 0b1100 // 结果: 0b1110 | | ^ | 位异或 | 0b1010 ^ 0b1100 // 结果: 0b0110 | | ~ | 位非 | ~0b1010 // 结果: 0b0101 | | << | 左移位 | 0b1010 << 1 // 结果: 0b10100 | 0b1010 << 2 // 结果: 0b101000 | | >> | 符号传播右移位 | (参见下文) | | >>> | 补零右移位 | (参见下文) |
注意,左移位实际上是乘以2,右位移则是除以2然后舍去尾数。
存在两种补码,最左边的二进制位为1时表示负数,0表示正数,它们都可以执行右移位。以−22为例。如果想要获取二进制表示法,以正数22开始,取反(反码)再加1(补码)。
let n = 22 // 32位二进制数: 00000000000000000000000000010110
n >> 1 // 00000000000000000000000000001011
n >>> 1 // 00000000000000000000000000001011
n = ~n // 取反: 11111111111111111111111111101001
n++ // 加1: 11111111111111111111111111101010
n >> 1 // 11111111111111111111111111110101
n >>> 1 // 01111111111111111111111111110101
除非从事的是硬件编程,或者想更好理解数字在计算机内部的表示原理,否则几乎用不上位运算符(一般被戏称为“玩位(味)”)。一个与硬件无关的使用场景可能是用二进制位来高效存储“标记位”(布尔值)。
例如,考虑一个Unix风格的文件权限:读、写和执行。一个给定的用户可以任意组合这三种权限,以达到预期目标。因为存在三种标志,所以需要三个二进制位来存储这些信息。
const FLAG_READ 1 // 0b001
const FLAG_WRITE 2 // 0b010
const FLAG_EXECUTE 4 // 0b100
有了位运算符,可以在一个单一的数值中组合、切换,以及检测每一个二进制位标记。
let p = FLAG_READ | FLAG_WRITE; // 0b011
let hasWrite = p & FLAG_WRITE; // 0b010 - truthy
let hasExecute = p & FLAG_EXECUTE; // 0b000 - falsy
p = p ^ FLAG_WRITE; // 0b001 – 写标记开关 (关闭)
p = p ^ FLAG_WRITE; // 0b011 – 写标记开关 (开启)
// 我们甚至可以确定在一个表达式中确定多个标记
const hasReadAndExecute = p & (FLAG_READ | FLAG_EXECUTE);
注意常量 hasReadAndExecute ,这里不得不使用分组运算符,因为与运算符比或运算符的优先级高,所以用括号来强制优先执行或运算。