前端进阶必备技能-JS位运算

前端进阶必备技能-JS位运算_第1张图片

1. 前言

      众所周知,位运算是我们学计算机必学的东西,平时的数组运算,其实是要先转换成二进制再进行运算的,而位运算就是直接进行二进制运算,位运算是低级的运算操作,所以速度往往也是最快的(相对其它运算如加减乘除来说),并且借助位运算的特性还能实现一些算法。恰当地使用运算有很多好处,前人用二进制、位运算给我们了一个操作简单的计算机,但我们却很少接触位运算了。所有的位运算都是在二进制下来进行运算的,再二进制下只有0和1。
下面通过一些实例来加深对位运算的理解。

关于二进制

      二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由18世纪德国数理哲学大师莱布尼兹发现。当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。计算机中的二进制则是一个非常微小的开关,用“开”来表示1,“关”来表示0。

前端进阶必备技能-JS位运算_第2张图片

2. 位运算符表

运算符 含义 举例 规则
& a&b 两个位都为1时,结果才为1。
| a|b 两个位都为0时,结果才为0。
~ 取反 ~a 0变1,1变0。
^ 异或 a^b 两个位相同为0,相异为1。
<< 左移位 a<<2 各二进位全部左移若干位,高位丢弃,低位补0。
>> 右移位 b>>4 各二进位全部右移若干位,对无符号数,高位补0,有符号数,右移补1
>>> 无符号右移位 x>>>2 各二进制全部右移若干位,高位补0,低位丢弃

2.1 位运算 - 逻辑结果参照

A B A&B A|B A^B
true true true true false
true false false true true
false true false true true
false false false false false
A B A&B A|B A^B ~A
0 0 0 0 0 1
1 0 0 1 1 0
0 1 0 1 1 1
1 1 1 1 0 0

2.2 位运算 - 实例

操作 结果 等同于 结果
5&1 1 0101 & 0001 0001
5 | 1 5 0101 | 0001 0101
5^1 4 0101 ^ 0001 0100
~5 10 ~0101 1010
5<<1 10 0101<<1 1010
5>>1 2 0101>>1 0010
5>>>1 2 0101>>>1 0010

下面举几个例子,主要看下 AND 和 OR:

# 例子1
    A = 10001001
    B = 10010000
A | B = 10011001

# 例子2
    A = 10001001
    C = 10001000
A | C = 10001001
# 例子1
    A = 10001001
    B = 10010000
A & B = 10000000

# 例子2
    A = 10001001
    C = 10001000
A & C = 10001000

2.3 位运算基础

运算符 - 图解例

前端进阶必备技能-JS位运算_第3张图片

3. 位运算使用方法有哪些

3.1、 按非运算符~在项目里边也很好用。

比如我们一般写代码的话,判断数组里边是否存在某一个值,是这样判断的

let arr = [4,7,10]
if (arr.indexOf(7) > -1) {
  console.log('测试')
}//测试

其实我们可以写的更简洁一点:

let arr = [4,7,10]
if (~arr.indexOf(7)) {
  console.log('测试')
}

3.2、 按左移运算符 << 迅速得出2的次方

console.log(1 << 5)  // 32, 即 2的5次方
console.log(1 << 10) // 1024, 即 2的10次方

// 但是要注意使用
console.log(a = 2e10) // 20000000000
console.log(a << 2)  // -1604378624

3.3、按 ^ 切换变量 0 或 1

//if 判断
let show;
if (show) {
  show = 0;
} else {
  show = 1;
}
console.log(show) // 1

//三目运算符
show = show ? 0 : 1;
console.log(show) // 1
//可以换成^
show ^= 1;
console.log(show) //1

## 3.4、按 & 判断奇偶性 ```javascript console.log(7 & 1); // 1 console.log(8 & 1) ; // 0 ```

3.5、有符号左移(<<)

有符号左移会将32位二进制数的所有位向左移动指定位数。如:

var num = 2; // 二进制10
num = num << 6; // 二进制1000000,十进制128
console.log(num)
//另外
function ceshi(n) {
  return 1 << n;
}
console.log(ceshi(5))//32

3.6、有符号右移(>>)

有符号右移会将32位二进制数的所有位向右移动指定位数。如:

var num = 64; // 二进制1000000
num = num >> 7; // 二进制10,十进制0
console.log(num)
//另外
var num = 64 >> 1; // 32
console.log(num)

3.7、无符号右移(>>>)

正数的无符号右移与有符号右移结果是一样的。负数的无符号右移会把符号位也一起移动,而且无符号右移会把负数的二进制码当成正数的二进制码:

var num = -64; // 11111111111111111111111111000000
num = num >>> 7; // 33554431
console.log(num)
//另外,我们可以利用无符号右移来判断一个数的正负:
function ceshi(n) {
  return (n === (n >>> 0)) ? true : false; 
}
console.log(ceshi(-1)) // false
console.log(ceshi(1)) // true

3.8、按位或(|)

|与||操作符的道理也是一样的,只要两个数中有一个数为1,结果就为1,其他则为0。

var num = Math.floor(1.3); // 1
console.log(num)//1
//另外
var num = 1.3 | 0; // 1
console.log(num)

等等 其他一些花里胡哨的操作这就不在举例了

4. 位运算推理

4.1.按位或 |

只有a和b任意一位为1时,a|b就是1

8 = 1 0 0 0
2 = 0 0 1 0
10 = 1 0 1 0

应用场景: 取整

function toInt(num) {
  return num | 0
}
console.log(toInt(1.9))        // 1
console.log(toInt(1.32322))    // 1

4.2.按位与 &

只有 a 和 b 都为1时,a & b 就是 1

8 = 1 0 0 0
2 = 0 0 1 0
0 = 0 0 0 0

应用场景:判断奇偶

var number = 0 
console.log(number & 1 === 1)

4.3,按位异或 ^

当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。

8 = 1 0 0 0
2 = 0 0 1 0
10 = 1 0 1 0

应用场景: 交换两个变量的值

let a = 5,
    b = 6;

console.log(a = a ^ b);//3
console.log(b = a ^ b);//5
console.log(a = a ^ b);//6

// 还可以通过运算
console.log(a = a + b);//11
console.log(b = a - b);//6
console.log(a = a - b);//5

// es 6
console.log([a, b] = [b, a])//[ 6, 5 ]
// 原理剖析:a = a ^ b; b = a ^ b 相当与 b = a ^ b ^ b = a ^ (b ^ b) = a ^ 0 = a;

4.4,按位非 ~

简单的说就是对一个二进制数进行取反,即0变成1,1变成零(零变一,一变零)
应用场景:小数取整,因为位运算的操作值要求是整数,其结果也是整数,所以经过位运算的都会自动变成整数

4.5,JS如何实现两个大数相加

当有两个整数 a 和 b ,在通常情况下我们有“+”运算符对其进行相加运算:

let sum = a + b;

但是 JS 的Number类型是有内存限制的,数字的长度超过8字节,便会损失精度。

JS 中整数的最大安全范围可以查到是:9007199254740991

为了避免出现精度损失,我们可以采用另一种方法来巧妙地运算
使用字符串进行计算
将每一个字符当做一位来单独进行计算,将其分出来非进位和进位和,将两者进行一个字符串的拼接

let a = "9007199254740991";
let b = "1234567899999999999";

function add(a ,b){
  //取两个数字的最大长度
  let maxLength = Math.max(a.length, b.length);
  //用0去补齐长度
  a = a.padStart(maxLength , 0);//"0009007199254740991"
  b = b.padStart(maxLength , 0);//"1234567899999999999"
  //定义加法过程中需要用到的变量
  let t = 0;	
  let f = 0;   //"进位"
  let sum = "";
  for(let i=maxLength-1 ; i>=0 ; i--){
    t = parseInt(a[i]) + parseInt(b[i]) + f;	//每一位字符的number和
    f = Math.floor(t/10);		//将和进行一个进位(类似<<)处理,并取出”进位“,这个进位要在下一位运算中加上
    sum = t%10 + sum;		//将第i位的字符结果拼接到sum上
  }
  if(f == 1){
    sum = "1" + sum;
  }
  return sum;
}

4.6,JS该不该用位运算?

      JS中最大数支持64bit,但是注意运算起来,如果是两个数,比如位操作,最大支持31bit(2147483647),如果超出之后,比如
2147483647 & 2147483647 结果与之后是32位,这个不支持已经溢出,所以安全的位操作的最大位数30位

2147483647 & 2147483647   // 2147483647
4294967295 & 4294967295   // -1

      没有什么必要在 js 中使用位运算,未必就能提高性能,引擎未必不会帮我们优化,由于 number 类型存储的方式,就可能会踩坑。

小结

      这套运算符针对的是整数,所以对 Javascript 完全无用,因为 Javascript 内部,所有数字都保存为双精度浮点数。如果使用它们的话,Javascript 不得不将运算数先转为整数,然后再进行运算,这样就降低了速度。而且"按位与运算符"&同"逻辑与运算符"&&,很容易混淆。

5. 写在最后:

      以上的例子在平常可能会比较容易用到或看到,也是属于比较容易理解的。一些比较复杂的、难理解的,对于我来说,觉得应该尽量少用,因为会给自己带来麻烦。但是巧妙的使用位运算可以大量减少运行开销,优化算法。我们都知道计算机存储的都是二进制数据,这是由计算机本身的设计决定的。

参考资料:

https://www.w3school.com.cn/js/js_bitwise.asp
https://blog.csdn.net/deaidai/article/details/78167367
https://blog.csdn.net/weixin_43808903/article/details/110262130

你可能感兴趣的:(javascript,前端)