[TOC]
> 查看《編程基礎(chǔ)》- 二進(jìn)制基礎(chǔ)
# JavaScript 位運(yùn)算
JavaScript 將數(shù)字存儲(chǔ)為 64 位浮點(diǎn)數(shù),但**所有按位運(yùn)算都以 32 位整型二進(jìn)制數(shù)執(zhí)行**。JavaScript 的浮點(diǎn)數(shù)遵循IEEE 754 規(guī)范。
在 ECMAScript 中,所有整數(shù)字面量默認(rèn)都是有符號(hào)整數(shù)。
位運(yùn)算只對(duì)整數(shù)起作用,如果一個(gè)運(yùn)算子不是整數(shù),會(huì)自動(dòng)轉(zhuǎn)為整數(shù)后再運(yùn)行。雖然在 JavaScript 內(nèi)部,數(shù)值都是以64位浮點(diǎn)數(shù)的形式儲(chǔ)存,但是做位運(yùn)算的時(shí)候,只能使用?31?位(0~2147483647),開發(fā)者是不能訪問(wèn)最高位的。
并且返回值也是一個(gè)32位帶符號(hào)的整數(shù)。
下圖展示的是數(shù) 18 的表示法:

1. 位運(yùn)算只發(fā)生在整數(shù)上,因此一個(gè)非浮點(diǎn)數(shù)參與位運(yùn)算之前會(huì)被向下取整,在控制臺(tái)輸入下面的代碼:
~~~js
2.1 | 0 // 或運(yùn)算
>>> 2
~~~
2. 為了避免訪問(wèn)符號(hào)位,?javascript?在現(xiàn)實(shí)?負(fù)數(shù)的?二進(jìn)制時(shí),轉(zhuǎn)換為?符號(hào)加上其絕對(duì)值的二進(jìn)制,如:
~~~
(-123).toString(2); // "-1111011",即?- 和 123 的二進(jìn)制
~~~
JavaScript 中的這種整型是區(qū)分正負(fù)數(shù)的,根據(jù)上面的知識(shí)推斷 js 中的整數(shù)的表示范圍是:
~~~
-Math.pow(2,31) ~ Math.pow(2,31)-1 // [-2^31, +2^31-1] 即 [-2147483648, +2147483647]
~~~
在控制臺(tái)輸出下面的代碼來(lái)驗(yàn)證我們的推斷。
~~~js
-2147483648 | 0
>>> -2147483648
-2147483649 | 0
>>> 2147483647
2147483647 | 0
>>> 2147483647
2147483648 | 0
>>> -2147483648
~~~
可以看出,如果一個(gè)超過(guò) `2^31-1` 的整數(shù)參與位運(yùn)算的時(shí)候就需要注意,其二進(jìn)制溢出了,截取32位后,如果第 32 位是 1 將被解讀為負(fù)數(shù)(補(bǔ)碼)。
所以:大于和小于最低和最高的值再去進(jìn)行轉(zhuǎn)換時(shí)都將改變正負(fù)號(hào)。
## 運(yùn)算符
| 運(yùn)算符 | 名稱 | 描述 |
| --- | --- | --- |
| `&` | AND(與) | 兩位都是 1 則設(shè)置每位為 1 |
| `| `| OR(或) | 兩位中有一個(gè)為 1 則設(shè)置每位為 1 |
| `^` | XOR(異或) | 兩位只有一位為 1 則設(shè)置每位為 1 |
| `~` | NOT(非) | 反轉(zhuǎn)所有位 |
| `<<` | 左移 | 通過(guò)從右填充 0 向左位移,并使最左邊的位移出 |
| `>>` | 有符號(hào)右位移 | 通過(guò)從左填充最左位的拷貝來(lái)向右位移,并使最右邊的位移出 |
| `>>>` | 無(wú)符號(hào)右移 | 通過(guò)從左填充 0 來(lái)向右位移,并使最右邊的位移出 |
由于 JavaScript 使用 32 位有符號(hào)整數(shù),`~ 5` 將返回 `-6`。
```js
00000000000000000000000000000101 // 5
11111111111111111111111111111011 // -5 (負(fù)數(shù)是正數(shù)的二進(jìn)制補(bǔ)碼加 1)
11111111111111111111111111111010 // ~5 = -6
```
1. 在執(zhí)行位運(yùn)算之前,JavaScript 將數(shù)字轉(zhuǎn)換為 32 位有符號(hào)整數(shù)。
2. 執(zhí)行按位操作后,結(jié)果將轉(zhuǎn)換回 64 位 JavaScript 數(shù)。
## 演示
> 使用 4 位無(wú)符號(hào)二進(jìn)制數(shù)進(jìn)行演示
| 操作 | 結(jié)果 | 等同于 | 結(jié)果 |
| --- | --- | --- | --- |
| `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 位無(wú)符號(hào)二進(jìn)制數(shù)。所以 `~ 5` 返回 10。
下面舉例子來(lái)說(shuō)明每個(gè)運(yùn)算符的作用,開始之前先來(lái)介紹幾個(gè)會(huì)用到的知識(shí)點(diǎn)
# 原生二進(jìn)制字面量
ES6 中引入了原生二進(jìn)制字面量,二進(jìn)制數(shù)的語(yǔ)法是`0b`開頭,我們將會(huì)用到這個(gè)新功能,chrome 最新版已經(jīng)支持。
~~~
0b111 // 7
0b001 // 1
(0b1100).toString(10) // 0b1100 === 12
~~~
js 中二進(jìn)制和十進(jìn)制如何轉(zhuǎn)換呢?
~~~
// 十進(jìn)制 => 二進(jìn)制
let num = 10;
console.log(num.toString(2));
// 二進(jìn)制 => 十進(jìn)制
let num1 = 1001;
console.log(parseInt(num1, 2));
~~~
## `Number.prototype.toString`
先來(lái)介紹下下面會(huì)用到的一個(gè)方法——`Number.prototype.toString`方法可以講數(shù)字轉(zhuǎn)化為字符串,有一個(gè)可選的參數(shù),用來(lái)決定將數(shù)字顯示為指定的進(jìn)制,下面可以查看3的二進(jìn)制表示
~~~
3..toString(2)
>> 11
~~~
## `& 與`
&按位與會(huì)將操作數(shù)和被操作數(shù)的相同為進(jìn)行與運(yùn)算,如果都為1則為1,如果有一個(gè)為0則為0
~~~
101
011
---
001
~~~
101 和 011與完的結(jié)果就是 001,下面在js中進(jìn)行驗(yàn)證
~~~
(0b101 & 0b011).toString(2)
>>> "1"
~~~
## `| 或`
|按位或是相同的位置上只要有一個(gè)為1就是1,兩個(gè)都為0則為0
~~~
101
001
---
101
~~~
101 和 001或完的結(jié)果是101,下面在js中進(jìn)行驗(yàn)證
~~~
(0b101 | 0b001).toString(2)
>>> "101"
~~~
## `~ 非`
`~`操作符會(huì)將操作數(shù)的每一位取反,如果是1則變?yōu)?,如果是0則邊為1
~~~js
101
---
010
~~~
101按位非的結(jié)果是010,下面在js中驗(yàn)證
~~~js
(~0b101).toString(2)
>>> "-110"
~~~
啊呀,怎么結(jié)果不對(duì)呢!??!上面提到了 js 中的數(shù)字是有符號(hào)的,我們忘記了最高位的符號(hào)了,為了簡(jiǎn)化我們將32位簡(jiǎn)化為8位,注意最高位是符號(hào)位
```
> 0 0000101
>
> 1 1111010 // 求非
>
> 1 0000101 // 求反
>
> 1 0000110 // 求補(bǔ)
```
`1 1111010`明顯是一個(gè)負(fù)數(shù),而且是負(fù)數(shù)的補(bǔ)碼表示,我們的求它的原碼,也就是再對(duì)它求補(bǔ)`1 0000110`就是這個(gè)數(shù)的真值,也就是結(jié)果顯示`-110`,這下總算自圓其說(shuō)了,O(∩_∩)O 哈哈~
其實(shí)上面的與和或也都是會(huì)操作符號(hào)位的,不信你試試下面這兩個(gè),可以看到符號(hào)位都參與了運(yùn)算
~~~js
(0b1&-0b1)
>>> 1
(0b1|-0b1)
>>> -1
~~~
## `^ 異或`
參與運(yùn)算的兩個(gè)值,如果兩個(gè)相應(yīng) bit 位相同,結(jié)果為0,否則為1:
按位異或的3個(gè)特點(diǎn):
1) `0^0=0,0^1=1` 0異或任何數(shù)=任何數(shù)
2) `1^0=1,1^1=0` 1異或任何數(shù)-任何數(shù)取反
3) 任何數(shù) 異或 自己 = 把自己置 0
~~~js
101
001
---
100
~~~
101 和 001異或的結(jié)果是 100,js中驗(yàn)證
~~~js
(0b101^0b001).toString(2)
>>> "100"
~~~
### 常見用途
1. 判斷兩個(gè)整數(shù)a,b是否相等,則可通過(guò)下列語(yǔ)句實(shí)現(xiàn):
```
(a ^ b) === 0
```
2. 使某些特定的位翻轉(zhuǎn)
例如:對(duì)數(shù)10100001的第2位和第3位翻轉(zhuǎn),則可以將該數(shù)與00000110進(jìn)行按位異或運(yùn)算。
```
10100001 ^ 00000110 = 10100111
```
3. 實(shí)現(xiàn)兩個(gè)值的交換,而不必使用臨時(shí)變量。
例如交換兩個(gè)整數(shù) a=10100001,b=00000110 的值,可通過(guò)下列語(yǔ)句實(shí)現(xiàn):
```
a = a ^ b; // a=10100111
b = b ^ a; // b=10100001
a = a ^ b; // a=00000110
```
4. 記錄多個(gè)信息
利用位的異或運(yùn)算使用一個(gè)數(shù)字記錄多個(gè)信息:
有幾個(gè)狀態(tài)值分別是?1、2、4、8、16?.....
| 二進(jìn)制表示 | 十進(jìn)制值 |
| --- | --- |
| 00000000000000000000000000000001 | 1 |
| 00000000000000000000000000000010 | 2 |
| 00000000000000000000000000000100 | 4 |
| 00000000000000000000000000001000 | 8 |
| 00000000000000000000000000010000 | 16 |
| 00000000000000000000000000100000 | 32 |
| 00000000000000000000000001000000 | 64 |
這些值的規(guī)律是,他們的二進(jìn)制只有一位是?1?,其余都是?0,?因此知道它們?nèi)我鈳讉€(gè)的按位異或運(yùn)算的結(jié)果,就知道是哪幾個(gè)數(shù)的組合,這樣可以用一個(gè)數(shù)字記錄多個(gè)信息。
```js
1^2^4?=?7??//?"00000111"
```
因此,如果我們知道結(jié)果是?7?,就知道它們是由?1?、2、4?組合而成。
5. 被用在一些加密算法中
## `<< 左移`
左移的規(guī)則將操作數(shù)向左移動(dòng)指定的位數(shù),右側(cè)用 0 補(bǔ)充,其效果相當(dāng)于 ×2,其實(shí)計(jì)算機(jī)就是用移位操作來(lái)計(jì)算乘法的
~~~js
010
---
0100
~~~
010左移一位就會(huì)變?yōu)?00,下面在js中驗(yàn)證
~~~
(0b010<<1).toString(2)
>>> "100"
~~~
## `>> 有符號(hào)右移`
該操作符會(huì)將第一個(gè)操作數(shù)向右移動(dòng)指定的位數(shù)。向右被移出的位被丟棄,拷貝最左側(cè)的位以填充左側(cè)。由于新的最左側(cè)的位總是和以前相同,符號(hào)位沒有被改變。所以被稱作“符號(hào)傳播”。
> 對(duì)任一數(shù)值 x 進(jìn)行右移n, 相當(dāng)于十進(jìn)制里的除以10的倍數(shù),在這里是指除以數(shù)之后取整。
> ~~~js
> x / 2^n
> ~~~
~~~js
(0b111>>1).toString(2)
>>> "11"
(-0b111>>1).toString(2)
>>> "-100"
~~~
負(fù)數(shù)的結(jié)果好像不太對(duì)勁,我們來(lái)看看是怎么回事
```
> -111 // 真值
> 1 0000111 // 原碼
> 1 1111001 // 補(bǔ)碼
> 1 1111100 // 算數(shù)右移
> 1 0000100 // 移位后的原碼
> -100 // 移位后的真值
```
## `>>> 無(wú)符號(hào)右移`
該操作符會(huì)將第一個(gè)操作數(shù)向右移動(dòng)指定的位數(shù)。向右被移出的位被丟棄,左側(cè)用0 填充。因?yàn)榉?hào)位變成了 0,所以結(jié)果總是非負(fù)的。對(duì)于整數(shù)和 `>>` 沒有區(qū)別。(譯注:即便右移 0 個(gè)比特,結(jié)果也是非負(fù)的。)
~~~
(0b111>>>1).toString(2)
>>> "11"
~~~
對(duì)于負(fù)數(shù)則就不同了,右移后會(huì)變?yōu)檎龜?shù)
~~~
(-0b111>>>1).toString(2)
>>> "1111111111111111111111111111100"
~~~
## 要注意的地方
如果運(yùn)算元不是可用的整數(shù),將取?0?作為運(yùn)算元:
```js
~NaN; // 將執(zhí)行 ~0 ,結(jié)果為 -1
~'x'; // -1
'hello'|0; // 0
({})|0 ; //0
~Infinity; //-1 同 ~0
```
位移運(yùn)算不能移動(dòng)超過(guò)31位,如果試圖移動(dòng)超過(guò)31位,將 位數(shù)?對(duì)32取模后再移位
```js
123 >> 32 //實(shí)際是 123>>0 (32%32 = 0)
123 >> 33 //實(shí)際是 123>>1
```
# 關(guān)于開頭的問(wèn)題
關(guān)于二進(jìn)制數(shù)就說(shuō)這么多吧,再來(lái)說(shuō)說(shuō)開頭的問(wèn)題,開頭的問(wèn)題其實(shí)可以分解為下面的問(wèn)題因?yàn)閟earch會(huì)返回-1 和找到位置的索引,也就成了下面的問(wèn)題
~~~
!~-1
>>> ture
!~0
>>> false
!~1
>>> false
~~~
非運(yùn)算對(duì)于數(shù)字的結(jié)果相當(dāng)于改變符號(hào),并對(duì)其值的絕對(duì)值-1
~~~
~-1
>>> 0
~0
>>> -1
~1
>>> -2
~~~
其實(shí)可以看出!~x的邏輯就是判斷x是否為-1,my god這邏輯真是逆天了,我還是勸大家直接寫成 x === -1多好啊>
> [聊聊JavaScript中的二進(jìn)制數(shù)](https://yanhaijing.com/javascript/2016/07/20/binary-in-js/)
# 參考
[w3school -JavaScript 位運(yùn)算符](https://www.w3school.com.cn/js/js_bitwise.asp)
[Javascript 中的二進(jìn)制運(yùn)算](https://www.cnblogs.com/ecalf/archive/2012/11/26/2789870.html)
- 步入JavaScript的世界
- 二進(jìn)制運(yùn)算
- JavaScript 的版本是怎么回事?
- JavaScript和DOM的產(chǎn)生與發(fā)展
- DOM事件處理
- js的并行加載與順序執(zhí)行
- 正則表達(dá)式
- 當(dāng)遇上this時(shí)
- Javascript中apply、call、bind
- JavaScript的編譯過(guò)程與運(yùn)行機(jī)制
- 執(zhí)行上下文(Execution Context)
- javascript 作用域
- 分組中的函數(shù)表達(dá)式
- JS之constructor屬性
- Javascript 按位取反運(yùn)算符 (~)
- EvenLoop 事件循環(huán)
- 異步編程
- JavaScript的九個(gè)思維導(dǎo)圖
- JavaScript奇淫技巧
- JavaScript:shim和polyfill
- ===值得關(guān)注的庫(kù)===
- ==文章==
- JavaScript框架
- Angular 1.x
- 啟動(dòng)引導(dǎo)過(guò)程
- $scope作用域
- $q與promise
- ngRoute 和 ui-router
- 雙向數(shù)據(jù)綁定
- 規(guī)范和性能優(yōu)化
- 自定義指令
- Angular 事件
- lodash
- Test
