我在看lodash实现一些工具函数的源码时发现lodash定义了一些bitMask
的常量。我一开始没弄明这是什么鬼东西,用Google搜了一圈才发现是我以前接触过得位操做运算一类的东西。而且源码和我搜索的资料给我提供了另外一种使用场景,感受应用性仍是蛮强的,因此干脆总结一下好了。算法
先如下面的表达式展开须要了解的基础知识。数组
// lodash 源码里定义的常量 var CLONE_DEEP_FLAG = 1
JavaScript遵循 IEEE 754 标准,不管是整数仍是小数都是用双精度浮点数表述,双精度浮点数8个字节,表示64位二进制位,因此双精度浮点数的表示范围是-2^63 ~ 2^63-1
。可是在进行位操做时则是用的32位数表示,也就是4个字节,表示范围为`-2^31 ~ 2^31-1,其中不管是32位仍是64位,最高位都是符号位,0表示正数,1表示负数。函数
上面的表达式在进行位操做就会转换成下面这种,若是超过32位了,那超过的部分就会所有省去。工具
00000000 00000000 00000000 00000001
下面介绍几种经常使用的操做符。加密
let a = 1, b = 2 console.log(a & b) // 0 // 0001 // 0010 // = 0000
把变量a
和变量b
都展开成32位二进制数,省去前面的0,a
的二进制表示为0001
,b
为0010
,接着就是对应位数的二进制位比较,若是相同就是1,不然为0。code
let a = 1, b = 2 console.log(a | b) // 3 // 0001 // 0010 // = 0011
参照上面一种,不一样的是相同的二进制位上,只要有一个是1,则结果就是1,因此就是0011
。ip
这个和|
有点区别,相同的地方在于若是同一位数上的数只要一个是1,则这个位数的结果就是1,不一样的地方在于相同的位数上若是数值相同,则结果为0.字符串
let a = 1, b = 2 console.log(a ^ b) // 3 // 0001 // 0010 // = 0011 let c = 3, d = 3 console.log(a ^ b) // 0 // 0011 // 0011 // = 0000
这个和以前三个最大的区别是对单个数的操做,而不是两个数的比较结果。简单来讲就是取反,对二进制上的每一位都取反。可是这里有个有趣的现象。源码
let a = 1; console.log(~ a) // -2
不管用~
取反任何数,得出的都是负数,并且是在正数上加一的负数。这里涉及三个概念:原码
,反码
,补码
。首先明确一点,负数是以补码的形式存在的。it
正数和负数的都是转换成二进制数后的样子,不一样的是负数的原码在最高位+1。
// 4的原码 00000000 000000000 000000000 00000100 // -4的原码 10000000 000000000 000000000 00000100
正数的反码和正数的原码一致。但负数的反码是对除了符号位上的其余二进制位取反。
// 4的反码 00000000 000000000 000000000 00000100 // -4的反码 11111111 11111111 11111111 11111011
正数的补码仍是和正数的原码一致,但负数的补码是在负数反码的基础上对最后一位加1。
// 4的补码 00000000 000000000 000000000 00000100 // -4的补码 11111111 11111111 11111111 11111100
因此说回上面提到的问题,3被~
转换为-4
的过程。
3:00000000 00000000 00000000 00000100 ~3:11111111 11111111 11111111 11111011 // ~3 这时候表示的是负数,那就按照原码->补码的顺序倒推 // 1.最低的位数-1 11111111 11111111 11111111 11111010 // 2.取反(除了符号位) 10000000 00000000 00000000 00000101 // 3.转换成十进制 -4
介绍完了几种操做符,来讲说有啥应用。
这是LeetCode上的一道题,题目是这样写的:
给定一个非空整数数组,除了某个元素只出现一次之外,其他每一个元素均出现两次。找出那个只出现了一次的元素。
这个用上面介绍过的^(异或)
操做符是最容易解答的,能够能够下^
的特性。题目里说的是只有一个数是惟一的,其余都是两两出现,而相同的数字,就像我上面举例用了两个3,结果是0,由于每一个二进制上的数都相同,因此结果就是每一个位上都变成了0.
function onlyNums(arr){ return arr.reduce((all,item) => all ^ item) }
这个例子就和咱们的平常贴的比较近了。后台系统进行权限配置的时候,通常可能就是定义几个字符串定义不一样的权限,若是一我的同时有不少权限,结果多是个数组,也多是把不一样权限字符串拼接成新的字符串。
let permission1 = 001 // 登陆权限 let permission2 = 002 // 建立权限 let permission3 = 003 // 删除权限 let adminPermission = '001,002,003' // or let adminPermission = [001,002,003]
若是换成位操做的思路:
let permission1 = 1 // 登陆权限 let permission2 = 2 // 建立权限 let permission3 = 4 // 删除权限 let permission4 = 8 // 编辑权限 let adminPermission = (permission1 | permission2 | permission3)
这个时候检查adminPermission
是否拥有某个权限就能够这样:
if((adminPermission & permission1) === permission1){ // 有登陆权限 }
删除某个权限:
adminPermission = adminPermission & (~ permission2)
新增某个权限:
adminPermission = adminPermission | permission4
除了上面两种场景,位操做还用于加密算法中,可是个人工做没有涉及过这方面,因此就不具体展开说了。感兴趣的话能够本身看看。