# 二進制位運算符
## 概述
二進制位運算符用于直接對二進制位進行計算,一共有7個。
- **二進制或運算符**(or):符號為`|`,表示若兩個二進制位都為`0`,則結果為`0`,否則為`1`。
- **二進制與運算符**(and):符號為`&`,表示若兩個二進制位都為1,則結果為1,否則為0。
- **二進制否運算符**(not):符號為`~`,表示對一個二進制位取反。
- **異或運算符**(xor):符號為`^`,表示若兩個二進制位不相同,則結果為1,否則為0。
- **左移運算符**(left shift):符號為`<<`,詳見下文解釋。
- **右移運算符**(right shift):符號為`>>`,詳見下文解釋。
- **頭部補零的右移運算符**(zero filled right shift):符號為`>>>`,詳見下文解釋。
這些位運算符直接處理每一個比特位(bit),所以是非常底層的運算,好處是速度極快,缺點是很不直觀,許多場合不能使用它們,否則會使代碼難以理解和查錯。
有一點需要特別注意,位運算符只對整數起作用,如果一個運算子不是整數,會自動轉為整數后再執行。另外,雖然在 JavaScript 內部,數值都是以64位浮點數的形式儲存,但是做位運算的時候,是以32位帶符號的整數進行運算的,并且返回值也是一個32位帶符號的整數。
```javascript
i = i | 0;
```
上面這行代碼的意思,就是將`i`(不管是整數或小數)轉為32位整數。
利用這個特性,可以寫出一個函數,將任意數值轉為32位整數。
```javascript
function toInt32(x) {
return x | 0;
}
```
上面這個函數將任意值與`0`進行一次或運算,這個位運算會自動將一個值轉為32位整數。下面是這個函數的用法。
```javascript
toInt32(1.001) // 1
toInt32(1.999) // 1
toInt32(1) // 1
toInt32(-1) // -1
toInt32(Math.pow(2, 32) + 1) // 1
toInt32(Math.pow(2, 32) - 1) // -1
```
上面代碼中,`toInt32`可以將小數轉為整數。對于一般的整數,返回值不會有任何變化。對于大于或等于2的32次方的整數,大于32位的數位都會被舍去。
## 二進制或運算符
二進制或運算符(`|`)逐位比較兩個運算子,兩個二進制位之中只要有一個為`1`,就返回`1`,否則返回`0`。
```javascript
0 | 3 // 3
```
上面代碼中,`0`和`3`的二進制形式分別是`00`和`11`,所以進行二進制或運算會得到`11`(即`3`)。
位運算只對整數有效,遇到小數時,會將小數部分舍去,只保留整數部分。所以,將一個小數與`0`進行二進制或運算,等同于對該數去除小數部分,即取整數位。
```javascript
2.9 | 0 // 2
-2.9 | 0 // -2
```
需要注意的是,這種取整方法不適用超過32位整數最大值`2147483647`的數。
```javascript
2147483649.4 | 0;
// -2147483647
```
## 二進制與運算符
二進制與運算符(`&`)的規則是逐位比較兩個運算子,兩個二進制位之中只要有一個位為`0`,就返回`0`,否則返回`1`。
```javascript
0 & 3 // 0
```
上面代碼中,0(二進制`00`)和3(二進制`11`)進行二進制與運算會得到`00`(即`0`)。
## 二進制否運算符
二進制否運算符(`~`)將每個二進制位都變為相反值(`0`變為`1`,`1`變為`0`)。它的返回結果有時比較難理解,因為涉及到計算機內部的數值表示機制。
```javascript
~ 3 // -4
```
上面表達式對`3`進行二進制否運算,得到`-4`。之所以會有這樣的結果,是因為位運算時,JavaScript 內部將所有的運算子都轉為32位的二進制整數再進行運算。
`3`的32位整數形式是`00000000000000000000000000000011`,二進制否運算以后得到`11111111111111111111111111111100`。由于第一位(符號位)是1,所以這個數是一個負數。JavaScript 內部采用補碼形式表示負數,即需要將這個數減去1,再取一次反,然后加上負號,才能得到這個負數對應的10進制值。這個數減去1等于`11111111111111111111111111111011`,再取一次反得到`00000000000000000000000000000100`,再加上負號就是`-4`。考慮到這樣的過程比較麻煩,可以簡單記憶成,一個數與自身的取反值相加,等于-1。
```javascript
~ -3 // 2
```
上面表達式可以這樣算,`-3`的取反值等于`-1`減去`-3`,結果為`2`。
對一個整數連續兩次二進制否運算,得到它自身。
```javascript
~~3 // 3
```
所有的位運算都只對整數有效。二進制否運算遇到小數時,也會將小數部分舍去,只保留整數部分。所以,對一個小數連續進行兩次二進制否運算,能達到取整效果。
```javascript
~~2.9 // 2
~~47.11 // 47
~~1.9999 // 1
~~3 // 3
```
使用二進制否運算取整,是所有取整方法中最快的一種。
對字符串進行二進制否運算,JavaScript 引擎會先調用`Number`函數,將字符串轉為數值。
```javascript
// 相當于~Number('011')
~'011' // -12
// 相當于~Number('42 cats')
~'42 cats' // -1
// 相當于~Number('0xcafebabe')
~'0xcafebabe' // 889275713
// 相當于~Number('deadbeef')
~'deadbeef' // -1
```
`Number`函數將字符串轉為數值的規則,參見《數據的類型轉換》一章。
對于其他類型的值,二進制否運算也是先用`Number`轉為數值,然后再進行處理。
```javascript
// 相當于 ~Number([])
~[] // -1
// 相當于 ~Number(NaN)
~NaN // -1
// 相當于 ~Number(null)
~null // -1
```
## 異或運算符
異或運算(`^`)在兩個二進制位不同時返回`1`,相同時返回`0`。
```javascript
0 ^ 3 // 3
```
上面表達式中,`0`(二進制`00`)與`3`(二進制`11`)進行異或運算,它們每一個二進制位都不同,所以得到`11`(即`3`)。
“異或運算”有一個特殊運用,連續對兩個數`a`和`b`進行三次異或運算,`a^=b; b^=a; a^=b;`,可以[互換](http://en.wikipedia.org/wiki/XOR_swap_algorithm)它們的值。這意味著,使用“異或運算”可以在不引入臨時變量的前提下,互換兩個變量的值。
```javascript
var a = 10;
var b = 99;
a ^= b, b ^= a, a ^= b;
a // 99
b // 10
```
這是互換兩個變量的值的最快方法。
異或運算也可以用來取整。
```javascript
12.9 ^ 0 // 12
```
## 左移運算符
左移運算符(`<<`)表示將一個數的二進制值向左移動指定的位數,尾部補`0`,即乘以`2`的指定次方。向左移動的時候,最高位的符號位是一起移動的。
```javascript
// 4 的二進制形式為100,
// 左移一位為1000(即十進制的8)
// 相當于乘以2的1次方
4 << 1
// 8
-4 << 1
// -8
```
上面代碼中,`-4`左移一位得到`-8`,是因為`-4`的二進制形式是`11111111111111111111111111111100`,左移一位后得到`11111111111111111111111111111000`,該數轉為十進制(減去1后取反,再加上負號)即為`-8`。
如果左移0位,就相當于將該數值轉為32位整數,等同于取整,對于正數和負數都有效。
```javascript
13.5 << 0
// 13
-13.5 << 0
// -13
```
左移運算符用于二進制數值非常方便。
```javascript
var color = {r: 186, g: 218, b: 85};
// RGB to HEX
// (1 << 24)的作用為保證結果是6位數
var rgb2hex = function(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b)
.toString(16) // 先轉成十六進制,然后返回字符串
.substr(1); // 去除字符串的最高位,返回后面六個字符串
}
rgb2hex(color.r, color.g, color.b)
// "#bada55"
```
上面代碼使用左移運算符,將顏色的 RGB 值轉為 HEX 值。
## 右移運算符
右移運算符(`>>`)表示將一個數的二進制值向右移動指定的位數。如果是正數,頭部全部補`0`;如果是負數,頭部全部補`1`。右移運算符基本上相當于除以`2`的指定次方(最高位即符號位參與移動)。
```javascript
4 >> 1
// 2
/*
// 因為4的二進制形式為 00000000000000000000000000000100,
// 右移一位得到 00000000000000000000000000000010,
// 即為十進制的2
*/
-4 >> 1
// -2
/*
// 因為-4的二進制形式為 11111111111111111111111111111100,
// 右移一位,頭部補1,得到 11111111111111111111111111111110,
// 即為十進制的-2
*/
```
右移運算可以模擬 2 的整除運算。
```javascript
5 >> 1
// 2
// 相當于 5 / 2 = 2
21 >> 2
// 5
// 相當于 21 / 4 = 5
21 >> 3
// 2
// 相當于 21 / 8 = 2
21 >> 4
// 1
// 相當于 21 / 16 = 1
```
## 頭部補零的右移運算符
頭部補零的右移運算符(`>>>`)與右移運算符(`>>`)只有一個差別,就是一個數的二進制形式向右移動時,頭部一律補零,而不考慮符號位。所以,該運算總是得到正值。對于正數,該運算的結果與右移運算符(`>>`)完全一致,區別主要在于負數。
```javascript
4 >>> 1
// 2
-4 >>> 1
// 2147483646
/*
// 因為-4的二進制形式為11111111111111111111111111111100,
// 帶符號位的右移一位,得到01111111111111111111111111111110,
// 即為十進制的2147483646。
*/
```
這個運算實際上將一個值轉為32位無符號整數。
查看一個負整數在計算機內部的儲存形式,最快的方法就是使用這個運算符。
```javascript
-1 >>> 0 // 4294967295
```
上面代碼表示,`-1`作為32位整數時,內部的儲存形式使用無符號整數格式解讀,值為 4294967295(即`(2^32)-1`,等于`11111111111111111111111111111111`)。
## 開關作用
位運算符可以用作設置對象屬性的開關。
假定某個對象有四個開關,每個開關都是一個變量。那么,可以設置一個四位的二進制數,它的每個位對應一個開關。
```javascript
var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000
```
上面代碼設置 A、B、C、D 四個開關,每個開關分別占有一個二進制位。
然后,就可以用二進制與運算,檢查當前設置是否打開了指定開關。
```javascript
var flags = 5; // 二進制的0101
if (flags & FLAG_C) {
// ...
}
// 0101 & 0100 => 0100 => true
```
上面代碼檢驗是否打開了開關`C`。如果打開,會返回`true`,否則返回`false`。
現在假設需要打開`A`、`B`、`D`三個開關,我們可以構造一個掩碼變量。
```javascript
var mask = FLAG_A | FLAG_B | FLAG_D;
// 0001 | 0010 | 1000 => 1011
```
上面代碼對`A`、`B`、`D`三個變量進行二進制或運算,得到掩碼值為二進制的`1011`。
有了掩碼,二進制或運算可以確保打開指定的開關。
```javascript
flags = flags | mask;
```
上面代碼中,計算后得到的`flags`變量,代表三個開關的二進制位都打開了。
二進制與運算可以將當前設置中凡是與開關設置不一樣的項,全部關閉。
```javascript
flags = flags & mask;
```
異或運算可以切換(toggle)當前設置,即第一次執行可以得到當前設置的相反值,再執行一次又得到原來的值。
```javascript
flags = flags ^ mask;
```
二進制否運算可以翻轉當前設置,即原設置為`0`,運算后變為`1`;原設置為`1`,運算后變為`0`。
```javascript
flags = ~flags;
```
## 參考鏈接
- Michal Budzynski, [JavaScript: The less known parts. Bitwise Operators](http://michalbe.blogspot.co.uk/2013/03/javascript-less-known-parts-bitwise.html)
- Axel Rauschmayer, [Basic JavaScript for the impatient programmer](http://www.2ality.com/2013/06/basic-javascript.html)
- Mozilla Developer Network, [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators)
- 前言
- 入門篇
- 導論
- 歷史
- 基本語法
- 數據類型
- 概述
- null,undefined 和布爾值
- 數值
- 字符串
- 對象
- 函數
- 數組
- 運算符
- 算術運算符
- 比較運算符
- 布爾運算符
- 二進制位運算符
- 其他運算符,運算順序
- 語法專題
- 數據類型的轉換
- 錯誤處理機制
- 編程風格
- console 對象與控制臺
- 標準庫
- Object 對象
- 屬性描述對象
- Array 對象
- 包裝對象
- Boolean 對象
- Number 對象
- String 對象
- Math 對象
- Date 對象
- RegExp 對象
- JSON 對象
- 面向對象編程
- 實例對象與 new 命令
- this 關鍵字
- 對象的繼承
- Object 對象的相關方法
- 嚴格模式
- 異步操作
- 概述
- 定時器
- Promise 對象
- DOM
- 概述
- Node 接口
- NodeList 接口,HTMLCollection 接口
- ParentNode 接口,ChildNode 接口
- Document 節點
- Element 節點
- 屬性的操作
- Text 節點和 DocumentFragment 節點
- CSS 操作
- Mutation Observer API
- 事件
- EventTarget 接口
- 事件模型
- Event 對象
- 鼠標事件
- 鍵盤事件
- 進度事件
- 表單事件
- 觸摸事件
- 拖拉事件
- 其他常見事件
- GlobalEventHandlers 接口
- 瀏覽器模型
- 瀏覽器模型概述
- window 對象
- Navigator 對象,Screen 對象
- Cookie
- XMLHttpRequest 對象
- 同源限制
- CORS 通信
- Storage 接口
- History 對象
- Location 對象,URL 對象,URLSearchParams 對象
- ArrayBuffer 對象,Blob 對象
- File 對象,FileList 對象,FileReader 對象
- 表單,FormData 對象
- IndexedDB API
- Web Worker
- 附錄:網頁元素接口
- a
- img
- form
- input
- button
- option
- video,audio