【JS】JavaScript 的按位操作符
据 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
所以二进制数参与计算的最优解是用补码计算。
总结一下原码、反码和补码的关系
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 = 6
a ^= b // 3
b ^= a // 5
a ^= 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_NORMAL
STATEFUL_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中的妙用
- 原码、反码、补码的产生、应用以及优缺点有哪些?
以上是 【JS】JavaScript 的按位操作符 的全部内容, 来源链接: utcz.com/a/99810.html