概
位运算符有 7 个,分为两类:
逻辑位运算符:位与(&)、位或(|)、位异或(^)、非位(~)
移位运算符:左移(<<)、右移(>>)、无符号右移(>>>)
左位移&右位移
“<<”运算符执行左移位运算。
在移位运算过程中,符号位始终保持不变。如果右侧空出位置
,则自动填充为 0
;超出 32 位的值,则自动丢弃。
“>>”运算符执行有符号右移位运算。
它把 32 位数字中的所有有效位整体右移,再使用符号位的值
填充空位
。移动过程中超出的值将被丢弃。
>>>
无符号右移
运算符执行五符号右移位运算。
它把无符号的 32 位整数所有数位整体右移。
对于无符号数或正数右移运算,无符号右移与有符号右移运算的结果是相同的。
~位非运算符
用于对一个二进制操作数逐位进行取反操作。
第 1 步:把运算数转换为 32 位的二进制整数。
第 2 步:逐位进行取反操作。
第 3 步:把二进制反码转换为十进制浮点数。
小数部分直接丢弃
运用:比如计算时间戳转为多少时分秒前,计算流量TGM,这时候需要每层取整。
- 数字取整
~~2.2 // 2 取反去掉了小数但符号是负数,再次取反为正。
~~2.8 // 2
2.2>>0 // 2
2.8>>0 // 2 写法比~~好,直接位操作,不用位运算两次
2.2<<0 // 2
2.8<<0 // 2
- 除数取整
1 >> 1 // 0 1/2 = 0
2 >> 1 // 1 2/2 = 1
3 >> 1 // 1 3/2 = 1
4 >> 1 // 2 4/2 = 2
5 >> 1 // 2 5/2 = 2
6 >> 1 // 3 6/2 = 3
7 >> 1 // 3 7/2 = 3
8 >> 1 // 4 8/2 = 4
公式:
n >> x
=>n / x
=>parseInt( n / x ^ 2 )
n除于x的2次方取整。x表示位移几个位置,在二进制中,一位是2的平方,n位即是2的n次方.
- 乘于2的x次方
3 << 3 // 3 * 2 ^ 3 == 24
公式:
n << x
=>n * 2 ^ x
非数值类型
非数值位运算时,会将操作值转为整型(就是0)。
而整型在内存中是64位双精度存储,位运算时会被转为32位。
在js中,超过32是直接截掉处理的。
位运算结束后又转为62位,这时候会丢失精度。
所以对非数值位运算,实际是对0取反,得到一定是-1
~非数值 // -1
判断是否是数字
let a = '1'
let b = 1
!isNan(a) // false
!isNan(b) // true
!!~~a // false
!!~~b // true
~a === -1 // true
~b === -1 // false
对负数
无论正负数,都会先执行符号位补0,再位移。
无符号右移将使用 0 来填充所有的空位,同时会把负数作为正数来处理。
所以负数永远得到一个很大的值。
永远返回正整数
-1 >>> 0 // 4294967295
运用技巧
操作数组
let arr = [1,2,3]
arr.splice(arr.indexOf(3) >>> 0, 1) // 获取数组中的某个值
使用无符号右移来判断下标是否存在,存在则切割该值返回切割后的数组,否则返回空数组。
如果不使用位移操作,会返回-1,得到的是数组的最后一个值,splice(-1, 1)
会从后面进行查找切割。
将判断和查找合在一起写,简化程序。
!~位反位移然后取反
隐藏哨位,内置函数的值暴露出来称为暴露哨位。
// 一般这么判断
if ('abc'.indexOf('a') === -1) // 找不到则...
// 改成方便我们直接用来判断的方式
if (!~'abc'.indexOf('a')) // 通过位移到0,取反则是找不到,不用判断等于-1
// 现在基本使用es6的include
if ('abc'.includes('a'))
//等同
if (!!~'abc'.indexOf('a')) // 找不到取反
其他:
- 双位反取整
- 除数取整