js位運算詳解及巧妙應用

位運算

JavaScript中的數字以浮點數的形式64位存儲。但在位運算中,數字被轉換爲有符號32位整數格式。每種位操作均直接在這32位數上實現結果,返回值也是有符號的32位整數。最後再將這32位的結果轉換回64位數值。

這種轉換導致了一個嚴重的副效應,即在對特殊的NaN和Infinity值應用位操作時,這兩個值都會被當成0處理。

如果對非數值應用位操作符,會先使用Number()函數將該值轉換爲一個數值(自動完成),然後再應用位操作。得到的結果將是一個數值。

js中的位運算有 按位或(|)按位與(&)按位非(~)按位異或(^)左移(<<)有符號右移(>>)無符號右移(>>>)。注意與邏輯運算符進行區分。

位運算的優先級很低,注意需要加上括號增加優先級。

按位或(|)

規則:有1爲1,全0爲0

這裏舉一個或運算的例子,兩個操作數一正一負(正數的二進制直接以原碼的形式存儲,負數以補碼的形式存儲)。

對於 -1 | 2
在這裏插入圖片描述
這裏由於32位太長,就直接把最後一位放在了第四位。可以看到,結果爲-1。

按位與(&)

規則:有0爲0,全1爲1

按位非(~)

規則:返回數值的反碼

本質:操作數的負值減1(-x-1)

按位異或(^)

規則:相同爲0,不同爲1

左移(<<)

規則:數值的所有位向左移動指定的位數,右側的空位用0補齊

特點:每左移一位,原操作數乘以一個2

藉此,我們可以利用有符號右移來代替所有的乘2或乘2^n運算。

注意: 左移不會影響操作數的符號位

有符號右移(>>)

規則:將數值向右移動,但保留符號位,左側空位用符號位補齊

特點:每右移一位,原操作數除以一個2

藉此,我們可以利用有符號右移來代替所有的除2乘2或除2^n運算。

無符號右移(>>>)

規則:將數值所有32位都向右移動,左側空位用0補齊

注意: 無符號右移操作會將負數的二進制碼(補碼)當成正數的二進制碼,因此結果往往很大。而該操作會將左側空位用0補齊,因此結果爲正數。

位運算的特點

位運算符較快 —— 當進行數學運算的時候,位運算操作比任何布爾運算或者算術運算快。選擇性地用位運算替換算術運算可以極大地提升複雜運算的性能。

同時,位運算也有着讓代碼可讀性變差的缺點。

位運算應用場景舉例

1. 判斷奇偶 —— 按位與(&)

由於偶數的最低位是0,奇數的最低位是1。那麼如果一個數是偶數,那麼它與1(二進制中只有最後一位爲1)進行位“與”操作的結果就是0,相反如果是奇數的話,結果就是1。

利用這個特性,我們可以利用num & 1來判斷奇偶。

return num & 1 === 1 ? '奇數' : '偶數';

利用位運算使這段代碼的運算速度比原始代碼提升了約50%。

2. 不借助第三個變量實現兩個變量的交換 —— 按位異或(^)

a = a ^ b;
b = a ^ b;
a = a ^ b;

當然也可以藉助ES6語法:

[a, b] = [b, a]

3. 判斷數組中某項是否存在 —— 按位取反(~)

利用有且僅有-1取反爲0:~-1 === 0

這個我們通常用在數組的indexOf方法中。

之前,我們是這樣寫的:

if(arr.indexof(item) > -1) {
    // TODO
}

現在,可以這麼寫:

if(~arr.indexof(item)) {
    // TODO
}

事實上,利用x取反的實質是-x-1這個特性,我們可以利用按位取反替換任何絕對值不大於2^32的整數。

4. 取整

注意: 對於大於2的32次方的整數,大於32位的數位都會被捨去。

由於0與任何數相或,都不會改變原數,而位運算會將數字轉爲整數,因此我們可以直接將操作數與0相或:

num | 0

同樣,我們還可以給num取兩次反:

~~num

或者左移0位/有符號右移0位:

num >> 0
num << 0

5. 位掩碼判斷用戶權限

使用位操作的技術稱爲位掩碼。位掩碼在計算機科學中是一種常用的技術,可用於同時判斷多個布爾選項,快速的將數字轉換成布爾標誌數組。掩碼中每個選項的值都是2的冪。

例如,我們來判斷用戶是否擁有某項權限:

const writable = 1;  // 可寫
const readable = 2;  // 可讀
const executable = 4;  //可執行

const currentUser = writable | readable;  // 當前用戶可讀、寫

if(currentUser | writable) { // 如果當前用戶可寫則執行代碼
    // TODO
}

if(currentUser | readable) { // 如果當前用戶可讀則執行代碼
    // TODO
}

6. 邊界判斷

假如我們有一個拖動事件,規定被拖動模塊需要在容器內部運動,這時就有邊界判斷,這其中又包括上,下,左,右四種單一邊界,同時還有類似上右,上左等疊加邊界,如果我們需要記錄這種狀態,通過位運算要比使用if判斷要簡單一些,上右下左四種邊界分別用1,2,4,8表示,代碼如下:

let flag = 0;
if (pos.left < left) flag = flag | 8; // 左
if (pos.bottom > bottom) flag = flag | 4; // 下
if (pos.right > right) flag = flag | 2; // 右
if (pos.top < top) flag = flag | 1; // 上
switch(flag) {
    // 上
    // 0 | 1
    case 1: 
    // 右
    // 0 | 2
    case 2: 
    // 右上
    // 0 | 1 | 2
    case 3:
    // 下
    // 0 | 4
    case 4:
    // 右下
    // 0 | 2 | 4
    case 6:
    // 左
    // 0 | 8
    case 8:
    // 左上
    // 0 | 8 | 1
    case 9:
    // 左下
    // 0 | 8 | 4
    case 12:
    // code
}

參考資料

《JavaScript高級程序設計3》

《編寫高質量代碼:改善JavaScript程序的188個建議》

深入研究js中的位運算及用法

JS位運算的巧妙運用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章