据 MDN 形容,按位操作符(Bitwise operators) 将其操作数(operands)当作32位的比特序列,即按位操作符的数会被转化成32位的二进制数进行操作。
在介绍 JavaScript 的按位操作符之前要先理解计算机二进制数中的几个基本概念:原码、反码和补码。
原码、反码和补码
如果咱们在内存中调配4位去寄存二进制数,负数十进制和二进制的关系如下图所示:
十进制 | 二进制(4位) |
---|---|
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 |
为了区别正数,引入了原码
概念,用右边的第一位数示意符号。如果是正数,那就要将右边第一位改成1,正数十进制对应的二进制4位原码如下图所示:
十进制 | 二进制(4位) |
---|---|
-0 | 1000 |
-1 | 1001 |
-2 | 1010 |
-3 | 1011 |
然而这样衍生出一个问题,负数和正数原码的和不为0。
// -1 + 10001 + 1001 = 1010 // -2
为了解决这个问题,呈现了 反码
概念。用原码求反码的办法是:负数不变,正数的符号为不变,其余位取反。正数十进制对应的二进制4位原码和反码对应表格如下图所示:
十进制 | 二进制原码(4位) | 二进制反码(4位) |
---|---|---|
-0 | 1000 | 1111 |
-1 | 1001 | 1110 |
-2 | 1010 | 1101 |
-3 | 1011 | 1100 |
此时再来算和
// -1 + 11001(原码) + 0001(原码) = 1110(反码) + 0001(反码) = 1111(反码) // 1111 就是 -0 的反码,所以后果是 -0
此时负数和正数的和是-0,在二进制原码中,-0 和 +0 的体现是有区别的,+0 是 0000,而 -0 是 1000,而 -1 与 1 的和显然不应该是 -0,这时候就又能够提出 补码
的概念:负数的补码是它自身,正数的补码是它的反码 + 1。正数十进制对应的二进制4位原码、反码和补码对应表格如下图所示:
十进制 | 二进制原码(4位) | 二进制反码(4位) | 二进制补码(4位) |
---|---|---|---|
-0 | 1000 | 1111 | 0000 |
-1 | 1001 | 1110 | 1111 |
-2 | 1010 | 1101 | 1110 |
-3 | 1011 | 1100 | 1101 |
能够看出 -0 的补码是 0000,而 +0 的原码是 0000,补码也是 0000,所以它们的补码统一。
// -1 + 11001(原码) + 0001(原码) = 1111(补码) + 0001(补码) = 0000(补码) // 0000 是 -0 和 0 专用的补码,所以 -1 + 1 的和是 0
所以二进制数参加计算的最优解是用补码计算。
总结一下原码、反码和补码的关系
非正数的原码 = 反码 = 补码
正数的反码 = 原码除符号位取反
正数的补码 = 反码 + 1
JavaScript 中的按位操作符
JavaScript 中的罕用按位操作符及形容如下表所示(下表格齐全照抄 MDN),所有的按位操作符的操作数都会被转成补码(two's complement)模式的有符号32位整数。
运算符 | 用法 | 形容 | |
---|---|---|---|
按位与( 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填充。 | |
有符号右移 | a >> b | 将 a 的二进制示意向右移 b (< 32) 位,抛弃被移出的位。 | |
无符号右移 | a >>> b | 将 a 的二进制示意向右移 b (< 32) 位,抛弃被移出的位,并应用 0 在左侧填充。 |
其中重点介绍一下上面两个
~ (按位非)
对每一个比特位执行非(NOT)操作。NOT a 后果为 a 的反转(即反码)。
5 (base 10) = 00000000000000000000000000000101 (base 2)~5 (base 10) = 11111111111111111111111111111010 (base 2) = -6 (base 10) // -6 的补码
对任一数值 x 进行按位非操作的后果为 -(x + 1)。
>> (有符号右移)
无符号左移和无符号右移填充的数据都是 0 ,而有符号右移填充的数据是最左侧数据。上面举两个例子
负数的有符号右移
负数的有符号右移在最右边填充 0
9 >> 29 (base 10): 0000000000000000000000000001001 (base 2)9 >> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)
正数的有符号右移在最右边填充 1
-1 >> 2-1 (base 10): 11111111111111111111111111111111 (base 2)-1 >> 2 (base 10): 11111111111111111111111111111111 (base 2) = -1 (base 10) // -1 的补码
按位操作符在 JavaScript 中的一些用法
说了这么多,还是要看看按位操作符在 Js 中具体有哪些利用场景。
用按位与 &
判断奇偶
用 按位与 &
能够给数判断奇偶
// 偶数 & 1 = 0// 基数 & 1 = 1
用按位异或 ^
数据交换
如果想实现两个数调换,然而不想新建变量,能够用异或 ^
let a = 5let b = 6a ^= b // 3b ^= a // 5a ^= b // 6
用标记位判断类型(标记位与掩码)
例子参考链接 枚举值 VNodeFlags,下文的图片和分类均出自该参考链接。Vue
组件的产出是 VNode
,渲染器渲染的指标也是 VNode
。在这里把 VNode
分成五类,别离是:html/svg
元素、组件、纯文本、Fragment 以及 Portal:
const VNodeFlags = { // html 标签 ELEMENT_HTML: 1, // SVG 标签 ELEMENT_SVG: 1 << 1, // 一般有状态组件 COMPONENT_STATEFUL_NORMAL: 1 << 2, // 须要被keepAlive的有状态组件 COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE: 1 << 3, // 曾经被keepAlive的有状态组件 COMPONENT_STATEFUL_KEPT_ALIVE: 1 << 4, // 函数式组件 COMPONENT_FUNCTIONAL: 1 << 5, // 纯文本 TEXT: 1 << 6, // Fragment FRAGMENT: 1 << 7, // Portal PORTAL: 1 << 8}
依据下面的分类图,能够派生出额定的三个标识。这里用了 按位或 |
操作符,
// html 和 svg 都是标签元素,能够用 ELEMENT 示意VNodeFlags.ELEMENT = VNodeFlags.ELEMENT_HTML | VNodeFlags.ELEMENT_SVG// 一般有状态组件、须要被keepAlive的有状态组件、曾经被keepAlice的有状态组件 都是“有状态组件”,对立用 COMPONENT_STATEFUL 示意VNodeFlags.COMPONENT_STATEFUL = VNodeFlags.COMPONENT_STATEFUL_NORMAL | VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE | VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE// 有状态组件 和 函数式组件都是“组件”,用 COMPONENT 示意VNodeFlags.COMPONENT = VNodeFlags.COMPONENT_STATEFUL | VNodeFlags.COMPONENT_FUNCTIONAL
这时咱们新建一个变量,并用 按位与 & 来判断它的类型
// STATEFUL_NORMAL_COMPONENT 是一个一般有状态组件const STATEFUL_NORMAL_COMPONENT = VNodeFlags. COMPONENT_STATEFUL_NORMALSTATEFUL_NORMAL_COMPONENT & VNodeFlags.COMPONENT_STATEFUL_NORMAL // 真STATEFUL_NORMAL_COMPONENT & VNodeFlags.COMPONENT_STATEFUL // 真STATEFUL_NORMAL_COMPONENT & VNodeFlags.COMPONENT // 真STATEFUL_NORMAL_COMPONENT & VNodeFlags.ELEMENT // 假
当然这里咱们也能够用数组的办法来判断,然而相比之下还是应用按位操作符更加简介和直观。
按位操作符的一些其余利用能够参考 位运算符在JS中的妙用,我这里只是举了几个例子。
参考
- MDN 按位操作符
- 位运算符在JS中的妙用
- 原码、反码、补码的产生、利用以及优缺点有哪些?