JS按位或"|"使用指南

2,163 阅读5分钟

概念与简单使用


单竖线原名叫作按位或(Bitewise OR)

先看下摘自MDN的一段解释:

Bitwise operators treat their operands as a sequence of 32 bits (zeroes and ones), rather than as decimal, hexadecimal, or octal numbers. For example, the decimal number nine has a binary representation of 1001. Bitwise operators perform their operations on such binary representations, but they return standard JavaScript numerical values.

大意是会将操作数都转为32位带符号整形二进制序列进行按位或(两个操作数的二进制每一位都进行或运算),但是最后返回的值还是标准的javascript数字(也就是十进制的数字)。

简单来说就是转成二进制后每一位都进行或运算再返回结果的十进制。

let one = 5;
let two = 3;
let result = 5 | 3; // 101 | 011 按位或结果就是111,转换为十进制就是7


因为会转化为32位整形二进制,小数部分会被舍弃,所以按位或也常用来取整。

let int = 2.8 | 0 // 2,舍弃小数部分后,整数部分与0进行按位或不会发生变动

有负数的情况

再来看一看MDN的片段

The operands of all bitwise operators are converted to signed 32-bit integers in two's complement format

这里专门提醒了是用补码进行运算,因为正数的补码本来就是原码,所以上面正数按位或的结果也并没有错。
let result = -5 | 3; // -5

/* 

记得之前说过两个操作会转变成"带符号位"的32位整形二进制吗,我们将这32位完整写出来

111111111111111111111111111011   -- -5的补码,负数的补码是反码加1
000000000000000000000000000011   -- 3的补码,也就是它本身
--------------------------------
111111111111111111111111111011   -- 结果的补码
100000000000000000000000000101   -- 结果的原码 -5

*/


有字符串/其他基本类型的情况

又又来看一下标准的描述

The operator ToInt32 converts its argument to one of 232 integer values in the range -231 through 231-1, inclusive. This operator functions as follows:

1. Call ToNumber on the input argument.

2. If Result(1) is NaN, +0, -0, +∞, or -∞, return +0.

大意是

1. 在转为32位整形时会先调用ToNumber(对于字符串就是Number(str),undefined会转为NaN,null会转为0,true转为1,false转为0)

2. 如果上一步的结果为NaN那就回返回0。

'abc' | 0 // 'abc'会先ToNumber, Nummber('abc')结果为NaN,第二步式子变为"0 | 0",最后结果为0
'0x0011' | 0 // '0x0011'同样先ToNumber,结果为17,式子变为"17 | 0",最后结果为17
undefined | 0 // undefined先ToNumber变为NaN,第二步式子变为"0 | 0",最后结果为0


有复杂类型的情况

这种情况会稍微复杂一点,在ToNumber之前会先调用ToPrimitive将对象转化为原始类型(ToNumber只能直接处理原始类型),再ToNumber。

* ToPrimitive它会先查找对象的valueOf方法,如果valueOf方法返回原始类型的值,则ToPrimitive的结果就是这个值,如果valueOf不存在或者valueOf方法返回的不是原始类型的值,就会尝试调用对象的toString方法,也就是会遵循对象的ToString规则,然后使用toString的返回值作为ToPrimitive的结果。

let obj = {};
console.log(obj | 0); // 0

/*

obj先调用ToPrimitive,结果为"[object Object]",接着调用ToNumber,结果为NaN,通过第二步
式子变为"0 | 0",结果为0

*/

obj = {
    valueOf() {
        return 10;
    }
}console.log(obj | 0) // 10



黑魔法

位操作是一种接近机器行为的操作,在腾讯IVWEB团队的《WebAssembly不完全指北》中,提到了如下一段


一段典型的asm.js代码如下:

可以看到,asm.js使用了按位或0的操作,来声明x为整形。从而确保JIT在执行过程中尽快生成相应的二进制代码,不用再去根据上下文判断变量类型。



设计权限系统


假设有这样一个场景,在一个应用中你需要设计一个系统来控制每一个角色的权限,借助位运算可以将权限设计变得简单且清晰。

let dele = 0b1000;
let add = 0b0100;
let update = 0b0010;
let select = 0b0001;

// 接着我们利用"|"给予权限
superadmin.auth =  dele | add | update | select; // 0b1111
adminOne.auth = add | select; // 0b0101
adminTwo.auth = update | select; // 0b0011
user.auth = select; // 0b0001

如果要实现"验证权限"就需要按位与(&),它的步骤和按位或一样,只是变为每一位相与

console.log(adminOne.auth & dele) // false

/*
利用按位与"&"验证权限

0101   -- adminOne.auth
1000   -- dele
------------------- 按位与
0000   -- false,验证失败

*/

要实现"删除权限"就需要先将要待删除权限按位取反(~)后再按位与(&)

adminTwo.auth = adminTwo.auth & ~update;
console.log(adminTwo.auth & update); // false

/*

利用按位与删除条目的反码删除权限

0011   -- adminOne.auth
1101   -- ~update
------------------- 按位与
0001   -- 新的权限已经不包含update
0010
------------------- 按位与
0000   -- false,update权限验证失败


*/

是不是让你想起了那一句chomd 777(111 111 111)呢。