目录
在很多情况下,可能很多前端的小伙伴都觉得位运算符能够做到的事情,通过普通的方式也能做到,何必花那么大的时间和功夫使用可读性有差,有绕来绕去的位运算符呢,就是为了装逼么!!
实则不然,大家都知道计算机其实是很笨的,他只能识别0和1两个信号,你传一个字符串给他,他鸟都不鸟你。那为什么我们写代码的时候写的什么字符串呀、对象呀什么的能够被计算及识别呢,其实是因为我们使用的语言已经对这些高级的语法特性做了一层“翻译”了,真正计算机这个方脑壳的数据,其实都是翻译过后的“01010010101001110101”。
而我们的位运算符,其实就是基于二进制的一个运算,这样的话,其实很多情况下就少了一个“翻译”的过程,在一个庞大和复杂的系统中,“翻译”阶段所耗费的时间其实也是很高的。因此,使用位运算符,在很大程度上能够让我们的程序有显著的性能上的提升(当然,如果你的项目很小,或者逻辑很简单的话,可能就没那么明显了),当然,同时也会在一定程度上降低代码的可读性,但是,相对于提升的性能来说,是相当值得的。
既然位运算符能够极大提升我们系统的性能,那我们就来看看js中都有哪些位运算符以及他们各自都有哪一些应用场景吧。
注:本文为了理解方便,将原本应该32位的二进制数省略为7位,js正常运算是二级制位一共有32位
-
按位与 &
- 定义:按位与运算符“&”是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位都为1时,结果位才为1。参与运算的两个数均以补码出现。(有零则零,双一为一)
- 举例:
-
0100100 1011111 ---------------以上两个数进行按位与,根据“有零则零,双一为一”的原则 0000100
-
- 应用场景:利用按位与只有当两个二进位都为1的时候才为1的特性,我们可以用来做一些权限判断(管理后台新增、修改、删除、查看等权限的控制)的判断,还可以用来判断一个数字是奇数还是偶数(奇数与1进行按位与运算都为1,偶数与1按位与运算都为0)等
-
let permission = { x: /* 只可执行 */ 0b001, w: /* 只可以写 */ 0b010, r: /* 只可以读 */ 0b100, }; let user1 = { name: 'kiner', permission: permission.r }; let user2 = { name: 'kanger', permission: permission.w }; let user3 = { name: 'tang', permission: permission.x }; let users = [user1, user2, user3]; users.forEach(user => { // 场景,判断用户有没有读取文件权限 if (user.permission & permission.r) { console.log(`用户:${user.name}可以读取文件`) } else { console.log(`用户:${user.name}不可以读取文件`) } // 场景,判断用户有没有写入文件权限 if (user.permission & permission.w) { console.log(`用户:${user.name}可以写入文件`) } else { console.log(`用户:${user.name}不可以写入文件`) } // 场景,判断用户有没有执行文件权限 if (user.permission & permission.x) { console.log(`用户:${user.name}可以执行文件`) } else { console.log(`用户:${user.name}不可以执行文件`) } });
以上权限判断输出结果如图:
-
按位或 |
- 定义:按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。当参与运算的是负数时,参与两个数均以补码出现。(“有一则一,双零为零”)
- 举例
-
0100100 1011111 ---------------以上两个数进行按位或,根据“有一则一,双零为零”的原则 1111111
-
- 应用场景: 当我们我们遇到一些需要多种情况场景成一个新的场景时可以使用按位与如:权限的组合。如上面的实例中只给出了只读、只写、只执行三个权限,但是还有其他情况如有读写权限或者是读写执行权限都有的情况,这个时候,我们就可以用按位与进行组合,具体示例如下:
-
let permission = { x: /* 只可执行 */ 0b001, w: /* 只可以写 */ 0b010, r: /* 只可以读 */ 0b100, }; permission.rw /* 可以读写 */ = permission.r | permission.w; permission.rx /* 可以读取和执行 */ = permission.r | permission.x; permission.wx /* 可以写入和执行 */ = permission.w | permission.x; permission.rwx /* 读取写入执行都可以 */ = permission.r | permission.w | permission.x; let user1 = { name: 'kiner', permission: permission.r }; let user2 = { name: 'kanger', permission: permission.rwx }; let user3 = { name: 'tang', permission: permission.rw }; let users = [user1, user2, user3]; users.forEach(user => { // 场景,判断用户有没有读取文件权限 if (user.permission & permission.r) { console.log(`用户:${user.name}可以读取文件`) } else { console.log(`用户:${user.name}不可以读取文件`) } // 场景,判断用户有没有写入文件权限 if (user.permission & permission.w) { console.log(`用户:${user.name}可以写入文件`) } else { console.log(`用户:${user.name}不可以写入文件`) } // 场景,判断用户有没有执行文件权限 if (user.permission & permission.x) { console.log(`用户:${user.name}可以执行文件`) } else { console.log(`用户:${user.name}不可以执行文件`) } });
-
异或 ^
- 定义:两二进制上下比较只有位不相等时才取1,否则取零(不同则一,相同为零,任何数跟他本身异或都是0,任何数跟0异或都是那个数)
- 举例:
-
0100100 1011111 ---------------以上两个数进行异或运算,根据“不同则一,相同为零”的原则 1111011
-
- 应用场景:LeetCode中的的136题:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素
-
/** * @param {number[]} nums * @return {number} */ var singleNumber = function(nums) { let len = nums.length; let res; // 异或运算符,两个数相同则为0,两个数不同则为1 // 如:二进制数: // 二进制数:001011001 // 二进制数:101011010 // 异或可得:100000011 // 任何数与本身进行异或运算都是0,任何数与0异或都是那个数本身 // 根据异或运算符的特点就可以轻松的到答案 // 如:[2,2,1] // 首先 2^2 两数相等 0 // 然后 0^1 和0异或 本身:1 // 这样我们就找到了这个鹤立鸡群的数了 while(len--){ res^=nums[len]; } return res; };
-
按位非 ~
- 定义:简单的说就是对一个二进制数进行取反,即0变成1,1变成零(零变一,一变零)
- 示例
-
0100100 ---------------以上二进制数进行按位非运算,根据“零变一,一变零”的原则 1011011
-
- 应用场景:小数取整,因为位运算的操作值要求是整数,其结果也是整数,所以经过位运算的都会自动变成整数。
-
console.log(~~2.23232);// 输出2
-
按位右移 >>
- 定义:顾名思义,就是将所有的二进制位往右移一位,前面空白位置补零,右边超出原来位数的舍弃,即如下实例中原本是7个0、1组成的二进制数,按位右移之后也还是7位,由于整体右移,把最右边的舍弃0舍弃掉,此时变成了6位,然后在左边补上0,又变成了7位。从示例中可以看出,由于二进制位整体右移,高位补零,导致其所表示的十进制数变小了,实际相当于做了Math.floor(a/2);
- 实例:
-
0100100 ---------------以上二进制数进行按位右移一位即 >>1 0010010
-
- 应用场景:比如我们要实现一个数除以二并取整的操作便可以采用按位右移
-
/** * 在字符串左边填充指定数量字符 */ function leftPad3(str, length, ch){ let len = length - str.length; let total = ''; while(len){ if(len&1){ total+=ch; } if(len==1){ return total + str; } ch += ch; // 通过按位右移,每次将当前的len除以二并取整 len = len >> 1; } } console.time('leftPad3'); for(let i=0;i<100000;i++){ leftPad3('hello',100000,'0') } console.timeEnd('leftPad3');
-
按位左移 <<
- 定义:“<<”运算符执行左移位运算。在移位运算过程中,符号位始终保持不变。如果右侧空出位置,则自动填充为 0;超出 32 位的值,则自动丢弃。
- 实例:
-
0100100 ---------------以上二进制数进行按位左移一位即 >>1 1001000
-
- 应用场景:将一个数值乘以2
-
console.log(10<<1);// 输出为20