51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

搞懂JavaScript中的位运算符

文章已同步至掘金:https://juejin.cn/post/7100867308867813389
欢迎访问😃,有任何问题都可留言评论哦~

前言 {#%E5%89%8D%E8%A8%80}

前段时间无意间在项目中看到了这么一行代码

 let x = m + n >> 1

虽然当时知道这是位运算符,但是对他的原理以及运算的结果还是有点不太清楚,遂搜集资料,整理了这篇文章

其实上面代码可以转化为:

let x = Math.floor((m + n) / 2)

逻辑运算符 {#%E9%80%BB%E8%BE%91%E8%BF%90%E7%AE%97%E7%AC%A6}

| 运算符 | 描述 | |------|-----| | && | and | | || | or | | ! | not |

位运算符 {#%E4%BD%8D%E8%BF%90%E7%AE%97%E7%AC%A6}

| 运算符 | 名称 | 描述 | |--------|--------|------------------------------| | & | AND | 如果两位都是 1 则设置每位为 1 | | | | OR | 如果两位之一为 1 则设置每位为 1 | | ^ | XOR | 如果两位只有一位为 1 则设置每位为 1 | | ~ | NOT | 反转所有位 | | << | 零填充左位移 | 通过从右推入零向左位移,并使最左边的位脱落。 | | >> | 有符号右位移 | 通过从左推入最左位的拷贝来向右位移,并使最右边的位脱落。 | | >>> | 零填充右位移 | 通过从左推入零来向右位移,并使最右边的位脱落。 |

特点 {#%E7%89%B9%E7%82%B9}

  • 在Javascript中使用位运算符,底层会先将数字转为整数(因为JS中所有的数字都保存为双精度浮点数),然后再进行计算(比如经常利用位运算符取整)
  • 位运算直接对二进制位进行计算(即按照内存中表示数值的位来操作数值),直接处理每一个比特位
  • 位操作符并不直接操作64位的值。而是先将64位的值转换为32位的整数,再执行操作,最后再将结果转换为64位

对于有符号的整数,32位中的后31位用于表示整数的值,第1位表示数值的符号:0表示正数,1表示负数。

假如有一个值为55转换成二进制为 101 ,转换过程:

js-bitwise-operators-1

但是因为它占用4字节(32位),所以前面填了一堆0,即 00000000 00000000 00000000 00000101

那么 -5 呢?

在计算机中,负数是以其正值的补码形式来表达的,如下

原码:一个整数,按照绝对值大小转换成的二进制数,称为原码
比如:5 的原码为 00000000 00000000 00000000 00000101

反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码(1变0,0变1)
比如:5 的反码为 11111111 11111111 11111111 11111010

`补码:反码加 1 称为补码
比如:5 的补码为 11111111 11111111 11111111 11111011
`

结论:

正数

正数以纯二进制格式存储,31位中的每1位都表示2的幂(还有1位是符号位)。第一位表示2º,第二位表示2¹,第三位表示2²,以此类推。没有用到的位以0表示(即忽略不计)。

负数

负数同样以二进制码存储,但使用的格式是二进制补码。计算一个数值的二进制补码(即求一个负数的二进制码),需要经过下列3个步骤:

  1. 求这个数值绝对值的二进制码
  2. 求二进制反码,即将0替换为1,将1替换为0
  3. 得到的二进制反码+1

原理 {#%E5%8E%9F%E7%90%86}

按位 与(AND):& {#%E6%8C%89%E4%BD%8D-%E4%B8%8E%EF%BC%88and)%EF%BC%9A%26}

& 以 特定的方式 组合操作 二进制 数中对应的位, 如果对应的位都为1,那么结果就是1。如果任意一个位是0,则结果就是0

1 的二进制:00000000 00000000 00000000 00000001
2 的二进制:00000000 00000000 00000000 00000010

1 \& 2 最终结果(也就是0):00000000 00000000 00000000 00000000


1 的二进制:00000000 00000000 00000000 00000001
3 的二进制:00000000 00000000 00000000 00000011

`1 & 3 最终结果(也就是1):00000000 00000000 00000000 00000001
`

按位 或(OR):| {#%E6%8C%89%E4%BD%8D-%E6%88%96%EF%BC%88or)%EF%BC%9A%7C}

| 运算符跟 & 的区别在于如果对应的位中任一个操作数为1,那么结果就是1

1 的二进制:00000000 00000000 00000000 00000001
2 的二进制:00000000 00000000 00000000 00000010

1 \| 2 最终结果(也就是3):00000000 00000000 00000000 00000011


1 的二进制:00000000 00000000 00000000 00000001
3 的二进制:00000000 00000000 00000000 00000011

`1 | 3 最终结果(也就是3):00000000 00000000 00000000 00000011
`

按位 异或(XOR):^ {#%E6%8C%89%E4%BD%8D-%E5%BC%82%E6%88%96%EF%BC%88xor)%EF%BC%9A%5E}

^ 如果对应两个操作位 有且仅有1个1时结果为1,其它都是0

1 的二进制:00000000 00000000 00000000 00000001
2 的二进制:00000000 00000000 00000000 00000010

1 \^ 2 最终结果(也就是3):00000000 00000000 00000000 00000011


1 的二进制:00000000 00000000 00000000 00000001
3 的二进制:00000000 00000000 00000000 00000011

`1 ^ 3 最终结果(也就是2):00000000 00000000 00000000 00000010
`

按位 非(NOT):~ {#%E6%8C%89%E4%BD%8D-%E9%9D%9E%EF%BC%88not)%EF%BC%9A~}

~ 运算符是对位求反,1变0,0变1,也就是求二进制的反码

2 的二进制:00000000 00000000 00000000 00000010

反码:11111111 11111111 11111111 11111101
由于第一位(符号位)是1,所以这个数是一个负数。
JavaScript 内部采用补码形式表示负数,即需要将这个数减去1,再取一次反,然后加上负号,才能得到这个负数对应的10进制值。
如下:


反码 -1
11111111 11111111 11111111 11111100


取反
00000000 00000000 00000000 00000011


加负号
\~2 的最终结果:-3


3 的二进制:00000000 00000000 00000000 00000011


反码:11111111 11111111 11111111 11111100


反码 -1
11111111 11111111 11111111 11111011


取反
00000000 00000000 00000000 00000100

`加负号
~3 的最终结果:-4
`

左移(Left shift):<< {#%E5%B7%A6%E7%A7%BB%EF%BC%88left-shift)%EF%BC%9A%3C%3C}

<< 运算符使指定的二进制数所有位都左移指定次数,其移动规则为:丢弃高位,低位补0(即按照二进制形式把所有的数字向左移动对应的位数,高位移除(舍弃),低位的空位补0)

1 的二进制:00000000 00000000 00000000 00000001

1 \<\< 2 (即左移2位)
00000000 00000000 00000000 00000001
00000000 00000000 00000000 0000000100


抛弃向左移动的两个0,后面补0,结果二进制如下
00000000 00000000 00000000 00000100
1 \<\< 2 的最终结果:4


1 \<\< 3 (即左移3位)
00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000001000

`抛弃向左移动的三个0,后面补0,结果二进制如下
00000000 00000000 00000000 00001000
1 << 3 的最终结果:8
`

结论:

M << N,相当于:M * (2**N)

有符号右移:>> (又称 "符号传播") {#%E6%9C%89%E7%AC%A6%E5%8F%B7%E5%8F%B3%E7%A7%BB%EF%BC%9A%3E%3E-%EF%BC%88%E5%8F%88%E7%A7%B0-%E2%80%9C%E7%AC%A6%E5%8F%B7%E4%BC%A0%E6%92%AD%E2%80%9D%EF%BC%89}

>> 该操作符会将指定操作数的二进制位向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧(这里是和无符号右移的最大区别)。由于新的最左侧的位总是和以前相同,而符号位并没有被改变。所以被称作"符号传播"

3 >> 2
3 的二进制:00000000 00000000 00000000 00000011

    00000000 00000000 00000000 00000011
      00000000 00000000 00000000 00000011




拷贝最前面两位00,抛弃向右移除的两位11,结果二进制如下
00000000 00000000 00000000 00000000
3 \>\> 2 最终结果:0


4 \>\> 1
4 的二进制:00000000 00000000 00000000 00000100

`00000000 00000000 00000000 00000100
00000000 00000000 00000000 00000100
拷贝最前面一位0,抛弃向右移除的一位0,结果二进制如下
000000000 00000000 00000000 0000010
4 >> 1 最终结果:2
`

无符号右移:>>> {#%E6%97%A0%E7%AC%A6%E5%8F%B7%E5%8F%B3%E7%A7%BB%EF%BC%9A%3E%3E%3E}

该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,左侧用0填充(这点是和有符号右移最大的区别)。因为符号位变成了0,所以结果总是非负的。

注:对于非负数,有符号右移和无符号右移总是返回相同的结果。

3 >>> 2
3 的二进制:00000000 00000000 00000000 00000011

00000000 00000000 00000000 00000011
00000000 00000000 00000000 00000011
拷贝最前面两位00,抛弃向右移除的两位11,结果二进制如下
00000000 00000000 00000000 00000000
3 \>\>\> 2 最终结果:0


4 \>\>\> 2
4 的二进制:00000000 00000000 00000000 00000100

`00000000 00000000 00000000 00000100
00000000 00000000 00000000 00000100
拷贝最前面一位0,抛弃向右移除的一位0,结果二进制如下
000000000 00000000 00000000 0000001
4 >>> 2 最终结果:1
`

应用 {#%E5%BA%94%E7%94%A8}

判断奇偶

// 偶数 & 1 = 0
// 奇数 & 1 = 1
console.log(2 & 1)    // 0
console.log(3 & 1)    // 1

取整

console.log(~~ 1.1)    // 6
console.log(1.1 >> 0)  // 6
console.log(1.1 << 0)  // 6
console.log(1.1 | 0)   // 6
// >>>不可对负数取整
console.log(1.1 >>> 0)   // 6

除以2向下取整

console.log(4 >> 1);
console.log(5 >> 1);

值交换

let m = 1
let n = 2
m ^= n
n ^= m
m ^= n
console.log(m)   // 2
console.log(n)   // 1

判断正负

console.log(1 === 1 >>> 0)
console.log(-1 === -1 >>> 0)

++ {#%2B%2B}

赞(1)
未经允许不得转载:工具盒子 » 搞懂JavaScript中的位运算符