JS 按位运算

前面看到MDN对数组 find polyfill 方法中使用了 o.length >>> 0 三个 >>> 的运算,看都看了那就顺便梳理下 javaScript 中的位运算符和实际的一些运用场景。

概述

  • 所有的按位操作符的操作数都会被转成 补码(two’s complement)形式的有符号32位整数。
  • 超过32位的数字会被丢弃,不足的通过 0 补齐。

这里先简单介绍下:原码反码补码 的概念。

原码

原码就是符号位加上真值的绝对值, 即用第一位表示符号(0为正,1为负), 其余位表示值。

例如:

  • 314 为:00000000000000000000000100111010
  • -31410000000000000000000000100111010

反码

  • 正数的反码是其本身
  • 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反。

例如:

  • 314 为:00000000000000000000000100111010
  • -31411111111111111111111111011000101

补码

  • 正数的补码就是其本身
  • 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

例如:

  • 314 为:00000000000000000000000100111010
  • -31411111111111111111111111011000110

临界值:

  • - (2 ** 31) 可表示最小值。是除了最左边为1外,其他比特位都为0的整数。10000000000000000000000000000000
  • (2 ** 31) - 1 可表示最大值。是除了最左边为0外,其他比特位都为1的整数。01111111111111111111111111111111

位运算符

& 按位与(AND)

对每对比特位执行与(AND)操作, 都是 1 时结果为 1 ,否则为 0

1
2
3
4
5
6
7
8
     // (9).toString(2) = 1001
9 (base 10) = 00000000000000000000000000001001 (base 2)

// (14).toString(2) = 1110
14 (base 10) = 00000000000000000000000000001110 (base 2)

// parseInt(1000, 2) = 8
14 & 9 (base 10) = 00000000000000000000000000001000 (base 2) = 8 (base 10)

使用

可用来判断奇偶数,偶数 & 1 = 0奇数 & 1 = 1

1
2
console.log(2 & 1)    // 0
console.log(3 & 1) // 1

| 按位或(OR)

对每一对比特位执行或(OR)操作, 如果 a 或 b 为 1 ,则结果为1,否则为 0

1
2
3
     9 (base 10) = 00000000000000000000000000001001 (base 2)
14 (base 10) = 00000000000000000000000000001110 (base 2)
14 | 9 (base 10) = 00000000000000000000000000001111 (base 2) = 15 (base 10)

^ 按位异或(XOR)

对每一对比特位执行异或(XOR)操作。当 a 和 b 不相同时,a XOR b 的结果为 1,否则为 0

1
2
3
     9 (base 10) = 00000000000000000000000000001001 (base 2)
14 (base 10) = 00000000000000000000000000001110 (base 2)
14 ^ 9 (base 10) = 00000000000000000000000000000111 (base 2) = 7 (base 10)

~ 按位非(NOT)

对每一个比特位执行非(NOT)操作。NOT a 结果为 a 的反转(即反码)

1
2
 9 (base 10) = 00000000000000000000000000001001 (base 2)
~9 (base 10) = 11111111111111111111111111110110 (base 2) = -10 (base 10)

使用

常见的和 indexOf 一起使用,-1 32位编码为 11111111111111111111111111111111,取按位非后就为 0

1
2
3
4
5
6
7
8
9
var str = 'rawr';
var searchFor = 'a';

// 这是 if (-1*str.indexOf('a') <= 0) 条件判断的另一种方法
if (~str.indexOf(searchFor)) {
// searchFor 包含在字符串中
} else {
// searchFor 不包含在字符串中
}

<< 左移(Left shift)

将 a 的二进制形式向左移 b (< 32) 比特位,右边用0填充。

例如, 9 << 2 得到 36:

1
2
     9 (base 10): 00000000000000000000000000001001 (base 2)
9 << 2 (base 10): 00000000000000000000000000100100 (base 2) = 36 (base 10)

在数字 x 上左移 y 比特得到 x * (2 ** y).

>> 有符号右移

将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,拷贝最左侧的位以填充左侧。

例如, 9 >> 2 得到 2:

1
2
     9 (base 10): 00000000000000000000000000001001 (base 2)
9 >> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)

相比之下, -9 >> 2 得到 -3,因为符号被保留了。

1
2
     -9 (base 10): 11111111111111111111111111110111 (base 2)
-9 >> 2 (base 10): 11111111111111111111111111111101 (base 2) = -3 (base 10)

由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。

使用

&, >>, | 来完成rgb值和16进制颜色值之间的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 16进制颜色值转RGB
* @param {String} hex 16进制颜色字符串
* @return {String} RGB颜色字符串
*/
function hexToRGB(hex) {
var hexx = hex.replace('#', '0x')
var r = hexx >> 16
var g = hexx >> 8 & 0xff
var b = hexx & 0xff
return `rgb(${r}, ${g}, ${b})`
}

/**
* RGB颜色转16进制颜色
* @param {String} rgb RGB进制颜色字符串
* @return {String} 16进制颜色字符串
*/
function RGBToHex(rgb) {
var rgbArr = rgb.split(/[^\d]+/)
var color = rgbArr[1]<<16 | rgbArr[2]<<8 | rgbArr[3]
return '#'+ color.toString(16)
}
// -------------------------------------------------
hexToRGB('#ffffff') // 'rgb(255,255,255)'
RGBToHex('rgb(255,255,255)') // '#ffffff'

>>> 无符号右移

将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。

对于非负数,有符号右移和无符号右移总是返回相同的结果。例如 9 >>> 2 和 9 >> 2 一样返回

1
2
      9 (base 10): 00000000000000000000000000001001 (base 2)
9 >>> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)

因为符号位变成了 0,所以结果总是非负的。(译注:即便右移 0 个比特,结果也是非负的。)

但是对于负数却不尽相同

1
2
      -9 (base 10): 11111111111111111111111111110111 (base 2)
-9 >>> 2 (base 10): 00111111111111111111111111111101 (base 2) = 1073741821 (base 10)

总结

实际的开发中尽量不要使用,不易读和理解,除非是在一些极致追求性能的场景用用吧。

参考资料