[TOC]
> 查看《编程基础》- 二进制基础
# JavaScript 位运算
JavaScript 将数字存储为 64 位浮点数,但**所有按位运算都以 32 位整型二进制数执行**。JavaScript 的浮点数遵循IEEE 754 规范。
在 ECMAScript 中,所有整数字面量默认都是有符号整数。
位运算只对整数起作用,如果一个运算子不是整数,会自动转为整数后再运行。虽然在 JavaScript 内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,只能使用 31 位(0~2147483647),开发者是不能访问最高位的。
并且返回值也是一个32位带符号的整数。
下图展示的是数 18 的表示法:
![](https://img.kancloud.cn/fb/1d/fb1d2685120a42de410367780716467d_551x74.png)
1. 位运算只发生在整数上,因此一个非浮点数参与位运算之前会被向下取整,在控制台输入下面的代码:
~~~js
2.1 | 0 // 或运算
>>> 2
~~~
2. 为了避免访问符号位, javascript 在现实 负数的 二进制时,转换为 符号加上其绝对值的二进制,如:
~~~
(-123).toString(2); // "-1111011",即 - 和 123 的二进制
~~~
JavaScript 中的这种整型是区分正负数的,根据上面的知识推断 js 中的整数的表示范围是:
~~~
-Math.pow(2,31) ~ Math.pow(2,31)-1 // [-2^31, +2^31-1] 即 [-2147483648, +2147483647]
~~~
在控制台输出下面的代码来验证我们的推断。
~~~js
-2147483648 | 0
>>> -2147483648
-2147483649 | 0
>>> 2147483647
2147483647 | 0
>>> 2147483647
2147483648 | 0
>>> -2147483648
~~~
可以看出,如果一个超过 `2^31-1` 的整数参与位运算的时候就需要注意,其二进制溢出了,截取32位后,如果第 32 位是 1 将被解读为负数(补码)。
所以:大于和小于最低和最高的值再去进行转换时都将改变正负号。
## 运算符
| 运算符 | 名称 | 描述 |
| --- | --- | --- |
| `&` | AND(与) | 两位都是 1 则设置每位为 1 |
| `| `| OR(或) | 两位中有一个为 1 则设置每位为 1 |
| `^` | XOR(异或) | 两位只有一位为 1 则设置每位为 1 |
| `~` | NOT(非) | 反转所有位 |
| `<<` | 左移 | 通过从右填充 0 向左位移,并使最左边的位移出 |
| `>>` | 有符号右位移 | 通过从左填充最左位的拷贝来向右位移,并使最右边的位移出 |
| `>>>` | 无符号右移 | 通过从左填充 0 来向右位移,并使最右边的位移出 |
由于 JavaScript 使用 32 位有符号整数,`~ 5` 将返回 `-6`。
```js
00000000000000000000000000000101 // 5
11111111111111111111111111111011 // -5 (负数是正数的二进制补码加 1)
11111111111111111111111111111010 // ~5 = -6
```
1. 在执行位运算之前,JavaScript 将数字转换为 32 位有符号整数。
2. 执行按位操作后,结果将转换回 64 位 JavaScript 数。
## 演示
> 使用 4 位无符号二进制数进行演示
| 操作 | 结果 | 等同于 | 结果 |
| --- | --- | --- | --- |
| `5 & 1` | 1 | 0101 & 0001 | 0001 |
| `5 | 1` | 5 | 0101 | 0001 | 0101 |
| `5 ^ 1` | 4 | 0101 ^ 0001 | 0100 |
| `~ 5` | 10 | ~0101 | 1010 |
| `5 << 1` | 10 | 0101 << 1 | 1010 |
| `5 >> 1` | 2 | 0101 >> 1 | 0010 |
| `5 >>> 1` | 2 | 0101 >>> 1 | 0010 |
上面的例子使用 4 位无符号二进制数。所以 `~ 5` 返回 10。
下面举例子来说明每个运算符的作用,开始之前先来介绍几个会用到的知识点
# 原生二进制字面量
ES6 中引入了原生二进制字面量,二进制数的语法是`0b`开头,我们将会用到这个新功能,chrome 最新版已经支持。
~~~
0b111 // 7
0b001 // 1
(0b1100).toString(10) // 0b1100 === 12
~~~
js 中二进制和十进制如何转换呢?
~~~
// 十进制 => 二进制
let num = 10;
console.log(num.toString(2));
// 二进制 => 十进制
let num1 = 1001;
console.log(parseInt(num1, 2));
~~~
## `Number.prototype.toString`
先来介绍下下面会用到的一个方法——`Number.prototype.toString`方法可以讲数字转化为字符串,有一个可选的参数,用来决定将数字显示为指定的进制,下面可以查看3的二进制表示
~~~
3..toString(2)
>> 11
~~~
## `& 与`
&按位与会将操作数和被操作数的相同为进行与运算,如果都为1则为1,如果有一个为0则为0
~~~
101
011
---
001
~~~
101 和 011与完的结果就是 001,下面在js中进行验证
~~~
(0b101 & 0b011).toString(2)
>>> "1"
~~~
## `| 或`
|按位或是相同的位置上只要有一个为1就是1,两个都为0则为0
~~~
101
001
---
101
~~~
101 和 001或完的结果是101,下面在js中进行验证
~~~
(0b101 | 0b001).toString(2)
>>> "101"
~~~
## `~ 非`
`~`操作符会将操作数的每一位取反,如果是1则变为0,如果是0则边为1
~~~js
101
---
010
~~~
101按位非的结果是010,下面在js中验证
~~~js
(~0b101).toString(2)
>>> "-110"
~~~
啊呀,怎么结果不对呢!!!上面提到了 js 中的数字是有符号的,我们忘记了最高位的符号了,为了简化我们将32位简化为8位,注意最高位是符号位
```
> 0 0000101
>
> 1 1111010 // 求非
>
> 1 0000101 // 求反
>
> 1 0000110 // 求补
```
`1 1111010`明显是一个负数,而且是负数的补码表示,我们的求它的原码,也就是再对它求补`1 0000110`就是这个数的真值,也就是结果显示`-110`,这下总算自圆其说了,O(∩_∩)O 哈哈~
其实上面的与和或也都是会操作符号位的,不信你试试下面这两个,可以看到符号位都参与了运算
~~~js
(0b1&-0b1)
>>> 1
(0b1|-0b1)
>>> -1
~~~
## `^ 异或`
参与运算的两个值,如果两个相应 bit 位相同,结果为0,否则为1:
按位异或的3个特点:
1) `0^0=0,0^1=1` 0异或任何数=任何数
2) `1^0=1,1^1=0` 1异或任何数-任何数取反
3) 任何数 异或 自己 = 把自己置 0
~~~js
101
001
---
100
~~~
101 和 001异或的结果是 100,js中验证
~~~js
(0b101^0b001).toString(2)
>>> "100"
~~~
### 常见用途
1. 判断两个整数a,b是否相等,则可通过下列语句实现:
```
(a ^ b) === 0
```
2. 使某些特定的位翻转
例如:对数10100001的第2位和第3位翻转,则可以将该数与00000110进行按位异或运算。
```
10100001 ^ 00000110 = 10100111
```
3. 实现两个值的交换,而不必使用临时变量。
例如交换两个整数 a=10100001,b=00000110 的值,可通过下列语句实现:
```
a = a ^ b; // a=10100111
b = b ^ a; // b=10100001
a = a ^ b; // a=00000110
```
4. 记录多个信息
利用位的异或运算使用一个数字记录多个信息:
有几个状态值分别是 1、2、4、8、16 .....
| 二进制表示 | 十进制值 |
| --- | --- |
| 00000000000000000000000000000001 | 1 |
| 00000000000000000000000000000010 | 2 |
| 00000000000000000000000000000100 | 4 |
| 00000000000000000000000000001000 | 8 |
| 00000000000000000000000000010000 | 16 |
| 00000000000000000000000000100000 | 32 |
| 00000000000000000000000001000000 | 64 |
这些值的规律是,他们的二进制只有一位是 1 ,其余都是 0, 因此知道它们任意几个的按位异或运算的结果,就知道是哪几个数的组合,这样可以用一个数字记录多个信息。
```js
1^2^4 = 7 // "00000111"
```
因此,如果我们知道结果是 7 ,就知道它们是由 1 、2、4 组合而成。
5. 被用在一些加密算法中
## `<< 左移`
左移的规则将操作数向左移动指定的位数,右侧用 0 补充,其效果相当于 ×2,其实计算机就是用移位操作来计算乘法的
~~~js
010
---
0100
~~~
010左移一位就会变为100,下面在js中验证
~~~
(0b010<<1).toString(2)
>>> "100"
~~~
## `>> 有符号右移`
该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧。由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。
> 对任一数值 x 进行右移n, 相当于十进制里的除以10的倍数,在这里是指除以数之后取整。
> ~~~js
> x / 2^n
> ~~~
~~~js
(0b111>>1).toString(2)
>>> "11"
(-0b111>>1).toString(2)
>>> "-100"
~~~
负数的结果好像不太对劲,我们来看看是怎么回事
```
> -111 // 真值
> 1 0000111 // 原码
> 1 1111001 // 补码
> 1 1111100 // 算数右移
> 1 0000100 // 移位后的原码
> -100 // 移位后的真值
```
## `>>> 无符号右移`
该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,左侧用0 填充。因为符号位变成了 0,所以结果总是非负的。对于整数和 `>>` 没有区别。(译注:即便右移 0 个比特,结果也是非负的。)
~~~
(0b111>>>1).toString(2)
>>> "11"
~~~
对于负数则就不同了,右移后会变为正数
~~~
(-0b111>>>1).toString(2)
>>> "1111111111111111111111111111100"
~~~
## 要注意的地方
如果运算元不是可用的整数,将取 0 作为运算元:
```js
~NaN; // 将执行 ~0 ,结果为 -1
~'x'; // -1
'hello'|0; // 0
({})|0 ; //0
~Infinity; //-1 同 ~0
```
位移运算不能移动超过31位,如果试图移动超过31位,将 位数 对32取模后再移位
```js
123 >> 32 //实际是 123>>0 (32%32 = 0)
123 >> 33 //实际是 123>>1
```
# 关于开头的问题
关于二进制数就说这么多吧,再来说说开头的问题,开头的问题其实可以分解为下面的问题因为search会返回-1 和找到位置的索引,也就成了下面的问题
~~~
!~-1
>>> ture
!~0
>>> false
!~1
>>> false
~~~
非运算对于数字的结果相当于改变符号,并对其值的绝对值-1
~~~
~-1
>>> 0
~0
>>> -1
~1
>>> -2
~~~
其实可以看出!~x的逻辑就是判断x是否为-1,my god这逻辑真是逆天了,我还是劝大家直接写成 x === -1多好啊>
> [聊聊JavaScript中的二进制数](https://yanhaijing.com/javascript/2016/07/20/binary-in-js/)
# 参考
[w3school -JavaScript 位运算符](https://www.w3school.com.cn/js/js_bitwise.asp)
[Javascript 中的二进制运算](https://www.cnblogs.com/ecalf/archive/2012/11/26/2789870.html)
- 步入JavaScript的世界
- 二进制运算
- JavaScript 的版本是怎么回事?
- JavaScript和DOM的产生与发展
- DOM事件处理
- js的并行加载与顺序执行
- 正则表达式
- 当遇上this时
- Javascript中apply、call、bind
- JavaScript的编译过程与运行机制
- 执行上下文(Execution Context)
- javascript 作用域
- 分组中的函数表达式
- JS之constructor属性
- Javascript 按位取反运算符 (~)
- EvenLoop 事件循环
- 异步编程
- JavaScript的九个思维导图
- JavaScript奇淫技巧
- JavaScript:shim和polyfill
- ===值得关注的库===
- ==文章==
- JavaScript框架
- Angular 1.x
- 启动引导过程
- $scope作用域
- $q与promise
- ngRoute 和 ui-router
- 双向数据绑定
- 规范和性能优化
- 自定义指令
- Angular 事件
- lodash
- Test