Javascript中的位运算

358 阅读3分钟

什么是位运算?

从现代计算机中所有的数据二进制的形式存储在设备中。即 0、1 两种状态,计算机对二进制数据进行的运算(+、-、*、/)都是叫位运算,即将符号位共同参与运算的运算。

二进制表示

JavaScript 将数字存储为 64 位浮点数,但所有按位运算都以 32 位二进制数执行。在执行位运算之前,JavaScript 将数字转换为 32 位有符号整数。执行按位操作后,结果将转换回 64 位 JavaScript 数。位运算只对整数起作用,如果一个运算子不是整数,会自动转为整数后再运行。

在 ECMAScript 中,所有整数字面量默认都是有符号整数,有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数。数值范围从 -2147483648 到 2147483647。

image.png

位运算符

符号描述运算规则
&两个位都为1时,结果才为1,有一个为0,结果就是0
|两个位都为0时,结果才为0,有一个为1时结果是1
异或两个位相同为0,不同为1
取反0变1,1变0
<<左移各个二进制位向左移动若干位,高位丢弃,低位补0
>>右移各个二进制位向右移动若干位,低位丢弃,高位补0(无符号数)
>>>无符号右移各个二进制位向右移动若干位,低位丢弃,高位补0(无符号数)

对任意数进行按位非操作的结果为-(x+1)

对任意数x进行左移,结果为 x * 2^n

对任意数x进行右移,结果为 x / 2^n

二进制与十进制转换

使用toStringparseInt进行进制转换:

// 十进制-> 二进制
num.toString(2)
// 二进制-> 十进制
parseInt(num,2)

位运算的用途

  1. 取整 将小数与0求或运算
1.2 | 0 // 1
~~(1.2)
  1. 边界判断 假如我们要判断鼠标指针是否在容器内,会存在 上、下、左、右、左上、左下、右上、右下这8钟状态,这时我们可以将标志位与1、2、4、8相或可以获得这8种状态:
let flag = 0;
if (pos.left < left) flag = flag | 8;
if (pos.right > right) flag = flag | 2;
if (pos.bottom > bottom) flag = flag | 4;
if (pos.top < top) flag = flag | 1;
switch(flag) {
    // 上
    case 1: 
    // 右
    case 2:
    // 右上
    case 3:
    // 下
    case 4:
    // 右下
    case 6:
    // 左
    case 8:
    // 左上
    case 9:
    // 左下
    case 12:
    // code
}

假如我们有一系列控制开关,通过 a | b | c的形式要比 '{a: true, b: true, c: true}' 简单的多

  1. 判断奇偶数 奇数二进制最后一位位1,所以奇数 & 1 === 1
num & 1 // 1:奇数,0:偶数

4.判断系统权限

我们假设某个管理系统有a, b, c, d四级权限,其中不同帐号分别有不同的权限(可能有1个或多个),例如admin 账户有a + b +c +d 四级权限,guest用户有b + c权限。 我们把权限分别用0001, 0010, 0100, 1000表示(即最通俗的1,2,4,8),如果admin用户有a, b, c, d四种权限,则admin的权限为 1 | 2 | 4 | 8 = 15,而guest用户权限为 4 | 8 = 12, 则判断用户是否有某种权限可以如下判断:

admin & 4 === 4
admin & 8 === 8
admin & 2 === 2
admin & 1 === 1
  1. 切换变量0、1
var flag = 0
flag = flag ^ 1 // 1
flag = flag ^ 1 // 0

6.交换两个变量值

let a= 1,b=2;
a=a^b;
b=a^b;
a=a^b;
// b:1,a:2
  1. 结合indexOf判断
~arr.indexOf(..) // ~-1===0

8.rgb和16进制色值转换

将RGB的3个数值分别转为十六进制数,然后拼接,即 rgb(255, 255, 255) => '#' + 'FF' + 'FF' + 'FF'。 巧妙利用左移,我们把十六进制数值部分当成一个整数,即FFFFFF,我们可以理解为FF0000 + FF00 + FF, 如同我们上面解释,如果左移是基于十六进制计算的,则可以理解为FF << 4, FF << 2, FF, 而实际上我们转为二进制则变为 FF << 16,如下: rgb->hex

function RGBToHex(rgb){
    // 取出rgb中的数值
    let arr = rgb.match(/\d+/g);
    if (!arr || arr.length !== 3) {
        console.error('rgb数值不合法');
        return
    }
    let hex = (arr[0]<<16 | arr[1]<<8 | arr[2]).toString(16);
    // 自动补全第一位
    if (hex.length < 6) {
        hex = '0' + hex;
    }
    return `#${hex}`;
}

hex->rgb

function hexToRGB(hex){
    if (!/^#([0-9a-fA-F]{3}){1,2}$/.test(hex)) {
        console.error('颜色不合法'); 
        return
    };
    // #f00 转为 #ff0000
    if (hex.length == 4) {
        hex = hex.replace(/([0-9a-fA-F])/g, '$1$1');
    };
    let num = hex.replace('#', '0x');
    let r = num >> 16;
    // 0xff = 255
    let g = num >> 8 & 0xff;
    let b = num  & 0xff;    
    return `rgb(${r},${g},${b})`;
}

参考资料