加载中...

深入理解JavaScript位运算符

吴佳
2020-07-10 11:12:42
分类:JavaScript
3792
1
0

前言

说起位运算符,各位一定是知道和二进制有关。但是我觉得,还是有大部分朋友对于位运算符还是比较陌生的,因为在实际的需求开发中这玩意几乎都没怎么用过,所以也就没有去过多的了解这东西。
其实,对于业务层来说这玩意是用的不多,但是对于源码层,看过vue或者react源码的朋友一定知道,在标记的时候是有用到位运算符去做类型区分的,我觉得这是一种很实用并且机智的做法。所以,我觉得我们是需要去弄清楚位运算符到底是怎么回事,怎么去计算的。那么我们废话少说,下面就来看看吧~

正文

在开始聊位运算符之前,我们需要先来聊一聊二进制,因为位运算与二进制是密不可分的。

二进制

所谓的二进制,其实简单点理解就是以32位数值来表示一串十进制数值的方式吧。因为我们现在程序里面用到的都是十进制数值,但是计算机内部计算会把十进制转换成二进制再进行计算。

我们都知道,整数有两种类型的,既:正数、负数。其实在二进制里面,它认为整数有两种类型,既有符号整数(也就是刚刚说的正数和负数)和无符号整数(其实就是正数,没有写+号罢了)。那么,二进制是如何表示一个十进制的数值呢?

我们刚刚说过,二进制是有32位数值来表示一个十进制数值的。其实有符号整数是使用31位数值来表示整数的数值,用第32位来表示整数的符号,0表示为正数,1表示为负数。而数值范围是从-2147483648到2147483647。

在存储数值的时候,是以两种不同方式来存储二进制形式的有符号整数:一种是存储正数、一种是存储负数。正数是以真二进制形式存储的,前31位中的每一位都表示2的幂,从第1位(位0)开始,表示 2的0次幂,第2位(位1)表示 2的1次幂,依次类推...。没用到的位用0填充,即忽略不计。

在网上找了张图,可以帮助大家理解一下

吴佳前端博客-位运算符1

从图中可以看到,开始位是在右边开始的,末位是在左边,所以这点是要注意的地方。
上图中是以数值18的二进制来做的示例,其有效位是前五位,即10010。我们用代码做个转换其实就可以看到有效位

let num = 18
console.log(num.toString(2)) // 10010

那么我们如何把二进制转换成十进制的呢?上面已经说了计算方法,下面我们来用代码做个换算,更深入的理解一下

// 18的二进制表示 10010,即我们从二进制右边到左边的幂次计算是这样的
(Math.pow(2, 4) * 1) + (Math.pow(2, 3) * 0) + (Math.pow(2, 2) * 0) + (Math.pow(2, 1) * 1) + (Math.pow(2, 0) * 0) = 18

根据上方的示例,我们就很清楚的看到了,从右至左分别从数值10010右边第一位进行按2的幂次相乘分别相加,最后得出十进制数值。

顺便我们再看一张我从网络上找的图,来加强理解吧

吴佳前端博客-位运算符1

其实负数也存储为二进制代码,不过采用的形式是二进制补码。计算数字二进制补码有三个步骤:

  • 确定该数字的非负版本的二进制表示(例如,要计算-18的二进制补码,首先要确定18的二进制表示)
  • 求得二进制反码,即需把 0 替换为 1,把 1 替换为 0
  • 在二进制反码上加 1

到这,我们就讲完了二进制相关的东西了,下面我们就开始讲讲位运算符。

位运算符

按位非(NOT):~

它的运算是取数值二进制的反码,然后将反码二进制数转成浮点数。反码的意思就是将二进制0和1的数值反转,上面已经说过了。

例如:

let num = 5 // 5的二进制 00000000000000000000000000000101
let num1 = ~5 // 取5的二进制反码 11111111111111111111111111111010
console.log(num1) // 最后得出 -6

换种方式理解,其实按位非NOT:~ 实际上是在给数值求负,然后减一。其实用下面这种方式同样可以得出以上结果

let num = 5
let num1 = -num - 1
console.log(num1) // 得出 -6

按位与(AND): &

它的运算规则是将两个操作数(二进制形式)的每一位对齐,跟据以下规则进行计算。

  • 只有同是1的时候,结果才是1
  • 其它任何情况都是0

例如:


let num = 5 & 10

/*
  5的二进制:00000000000000000000000000000101
  10的二进制:00000000000000000000000000001010
  -------------------------------------------
  运算得出:00000000000000000000000000000000
*/
// 最后将运算得出的二进制转为十进制,即按照上方说的计算方式计算
console.log(num) // 0

按位或(OR): |

同样它的运算规则也是将两个操作数(二进制形式)的每一位对齐,然后跟据以下规则进行计算。

  • 只有同是0的时候,结果才是0;
  • 其它任何情况都是1;

例如:


let num = 5 | 10

/*
  5的二进制:00000000000000000000000000000101
  10的二进制:00000000000000000000000000001010
  -------------------------------------------
  运算得出:00000000000000000000000000001111
*/
// 最后将运算得出的二进制转为十进制,即按照上方说的计算方式计算
Math.pow(2, 3) * 1 + Math.pow(2, 2) * 1 + Math.pow(2, 1) * 1 + Math.pow(2, 0) * 1
console.log(num) // 最后得出 15

按位异或(XOR): ^

它的运算规则同样是将两个操作数(二进制形式)的每一位对齐,然后跟据以下规则进行计算。

  • 两位同是0或1的时候,结果才是0;
  • 其它任何情况都是1;

例如:


let num = 5 ^ 4

/*
  5的二进制:00000000000000000000000000000101
  4的二进制:00000000000000000000000000000100
  -------------------------------------------
  运算得出:  00000000000000000000000000000001
*/
// 最后将运算得出的二进制转为十进制,即按照上方说的计算方式计算
Math.pow(2, 0) * 1
console.log(num) // 得出 1

左移:<<

这个操作符会将数值的所有位向左移动指定的位数。右边空出来的位置,补0;

例如:

let num = 5 << 4

/*
  5 的二进制:   00000000000000000000000000000101
  ---------------------------------------------
  向左移动四位: 00000000000000000000000001010000  
*/
// 最后将移动得出的二进制转为十进制,即按照上方说的计算方式计算
Math.pow(2, 6) * 1 + Math.pow(2, 5) * 0 + Math.pow(2, 4) * 1
console.log(num) // 得出 80

有符号右移:>>

这个操作符会将数值向右移动,但保留符号位(即正负号标记)。有符号的右移操作与左移操作恰好相反。

例如:

let num = 8 >> 3

/*
  8 的二进制:   00000000000000000000000000001000
  ---------------------------------------------
  向右移动三位: 0 0000000000000000000000000000001 
  第32位保持不动,从第31位开始往后推3位
*/
// 最后将移动得出的二进制转为十进制,即按照上方说的计算方式计算
Math.pow(2, 0) * 1
console.log(num) // 得出 1

无符号右移:>>>

这个操作符会将数值的所有32位都向右移动,对正数来说,无符号右移的结果与有符号右移相同。
但是对负数来说,就不一样了。首先,无符号右移是以0来填充空位,而不是像有符号右移那样以符号位之前的值来填充空位。所以,对正数的无符号右移与有符号右移结果相同,但对负数的结果就不一样了。
其次,无符号右移操作符会把负数的二进制码当成正数的二进制码。
而且,由于负数以其绝对值的二进制补码形式表示,因此就会导致无符号右移后的结果非常大。

例如:

let num = -14 >>> 2

/*
  -14 的二进制:11111111111111111111111111110010
  ---------------------------------------------
  向右移动2位: 00111111111111111111111111111100
*/
// 最后将移动得出的二进制转为十进制,即按照上方说的计算方式计算
// 由于数值过多,计算过长。所以这里我就直接用函数递归方式计算
 function sum(n = 29, a = 0) {
    if (n > 1) {
       let d = Math.pow(2, n) * 1
       a = a + d
      return sum(--n, a)
    }
    return a
 }
 sum() // 1073741820
 console.log(num) // 得出 1073741820

扫码关注后,回复“资源”免费领取全套视频教程

前端技术专栏

1

发表评论(共0条评论)

请输入评论内容
啊哦,暂无评论数据~