JavaScript中按位操作符的有趣應(yīng)用

前言

今日早讀文章由騰訊@CoyPan翻譯授權(quán)分享。

正文從這開(kāi)始~~

JavaScript提供了幾種運(yùn)算符苛白,可以對(duì)一些簡(jiǎn)單的值進(jìn)行基本操作娃豹,比如算術(shù)操作、賦值操作购裙、邏輯操作懂版、按位操作等。

我們經(jīng)初锫剩可以看到混合了賦值操作躯畴,算術(shù)操作和邏輯操作的JavaScript代碼。但是薇芝,按位操作的代碼就不是那么常見(jiàn)了蓬抄。

JavaScript的按位操作符

  • ~ (按位非)

  • & (按位與)

  • | (按位或)

  • ^ (按位異或)

  • << (左移)

  • >> (有符號(hào)右移)

  • >>> (無(wú)符號(hào)右移)

在本文中,我們將過(guò)一遍所有的按位操作符并且試著理解他們是怎么工作的恩掷。同時(shí)倡鲸,我們會(huì)編寫(xiě)簡(jiǎn)單的JavaScript的代碼供嚎,來(lái)看一看一些有趣的按位操作符運(yùn)用黄娘。這需要我們了解一下javascript位操作符如何將其操作數(shù)表示為有符號(hào)的32位整數(shù)。讓我們開(kāi)始吧克滴。

按位非(~)

運(yùn)算符是一元運(yùn)算符逼争;因此,它只需要一個(gè)操作數(shù)劝赔。運(yùn)算符對(duì)其操作數(shù)的每一位執(zhí)行NOT操作誓焦。非運(yùn)算的結(jié)果稱為補(bǔ)碼。整數(shù)的補(bǔ)碼是通過(guò)將整數(shù)的每一位倒轉(zhuǎn)而形成的着帽。

對(duì)于給定的整數(shù)(例如170)杂伟,可以使用~運(yùn)算符計(jì)算補(bǔ)碼,如下所示:

// 170 => 00000000000000000000000010101010
// --------------------------------------
//  ~ 00000000000000000000000010101010
// --------------------------------------
//  = 11111111111111111111111101010101
// --------------------------------------
//  = -171 (decimal)
console.log(~170); // -171

javascript按位運(yùn)算符將其操作數(shù)轉(zhuǎn)換為二進(jìn)制補(bǔ)碼格式的32位有符號(hào)整數(shù)仍翰。因此赫粥,當(dāng)對(duì)整數(shù)使用~運(yùn)算符時(shí),得到的值是整數(shù)的補(bǔ)碼予借。整數(shù)A的補(bǔ)碼的結(jié)果為 - (A+1) 越平。

~170 => -(170 + 1) => -171

下面是一些需要注意的關(guān)于32位有符號(hào)整數(shù)的要點(diǎn)频蛔,這些整數(shù)由javascript位運(yùn)算符使用:

  • 最有意義(最左邊)的位稱為符號(hào)位。正整數(shù)的符號(hào)位總是0秦叛,負(fù)整數(shù)的符號(hào)位總是1晦溪。

  • 除符號(hào)位之外的其余31位用于表示整數(shù)。因此挣跋,可以表示的最大32位整數(shù)是(2^32-1)三圆,它是2147483647,而最小整數(shù)是(2^31)浆劲,它是-2147483648嫌术。

  • 對(duì)于不在32位有符號(hào)整數(shù)范圍內(nèi)的整數(shù),最有效位將被丟棄牌借,直到整數(shù)在該范圍內(nèi)度气。

以下是一些重要數(shù)字的32位序列表示:

0 => 00000000000000000000000000000000 -1 => 11111111111111111111111111111111 2147483647 => 01111111111111111111111111111111 -2147483648 => 10000000000000000000000000000000

從上面的描述可以很容易得出:

          ~0 => -1         ~-1 => 0

找到索引

大多數(shù)JavaScript內(nèi)置對(duì)象(如數(shù)組和字符串)都有一些有用的方法,可用于檢查數(shù)組中是否存在項(xiàng)或字符串中是否存在子字符串膨报。以下是一些方法:

  • Array.indexOf()

  • Array.lastIndexOf()

  • Array.findIndex()

  • String.indexOf()

  • String.lastIndexOf()

  • String.search()

這些方法都返回某一項(xiàng)或子字符串的從零開(kāi)始的索引(如果找到)磷籍;否則,它們返回-1现柠。例如:

const numbers = [1, 3, 5, 7, 9];console.log(numbers.indexOf(5)); // 2console.log(numbers.indexOf(8)); // -1

如果我們對(duì)么某一項(xiàng)或者子字符串的索引位置不感興趣院领,我們可以選擇使用布爾值。當(dāng)未找到的項(xiàng)或者子字符串時(shí)够吩,返回-1比然,我們可以認(rèn)為是false,返回其他的值都是true周循。

function foundIndex (index) {

在上面的代碼片段中强法,~運(yùn)算符在-1上使用時(shí)的值為0。使用boolean()將值強(qiáng)制轉(zhuǎn)換為boolean湾笛,返回false饮怯。對(duì)于其他每個(gè)索引值,返回true嚎研。因此蓖墅,以前的代碼段可以修改如下:

const numbers = [1, 3, 5, 7, 9];console.log(foundIndex(numbers.indexOf(5))); // trueconsole.log(foundIndex(numbers.indexOf(8))); // false

按位與(&)

& 操作符對(duì)其操作數(shù)的每一對(duì)對(duì)應(yīng)位執(zhí)行一個(gè)和運(yùn)算。& 操作符僅當(dāng)兩個(gè)位都為1時(shí)返回1临扮;否則返回0论矾。因此,與運(yùn)算的結(jié)果等于將每一對(duì)對(duì)應(yīng)的位相乘杆勇。

下面是與操作的可能值:

(0 & 0) === 0 // 0 x 0 = 0 (0 & 1) === 0 // 0 x 1 = 0 (1 & 0) === 0 // 1 x 0 = 0 (1 & 1) === 1 // 1 x 1 = 1

‘關(guān)閉’某些位

&操作符通常用于位屏蔽應(yīng)用贪壳,以確保為給定的位序列關(guān)閉某些位。這是基于這樣一個(gè)事實(shí)靶橱,即對(duì)于任何位A:

  • (A & 0 = 0) — 和0進(jìn)行與運(yùn)算寥袭,位總是會(huì)變成0路捧。

  • (A & 1 = A) — 和1進(jìn)行與運(yùn)算,位總是保持不變传黄。

舉個(gè)例子杰扫,假設(shè)我們有一個(gè)8位的整數(shù),我們希望確保前面的4位被關(guān)閉(置為0)膘掰。我們可以用&操作符來(lái)實(shí)現(xiàn):

首先章姓,創(chuàng)建一個(gè)位掩碼,其效果是關(guān)閉8位整數(shù)的前4位识埋。該位掩碼將為0B111110000凡伊。請(qǐng)注意,位掩碼的前4位設(shè)置為0窒舟,而其他每一位設(shè)置為1系忙。

接下來(lái),使用8位整數(shù)和創(chuàng)建的位掩碼進(jìn)行 &操作惠豺。

const mask = 0b11110000;
// 222 => 11011110
// (222 & mask)
// ------------
// 11011110
// & 11110000
// ------------
// = 11010000
// ------------
// = 208 (decimal)


console.log(222 & mask); // 208

檢查設(shè)定位

&操作符還有一些其他有用的位屏蔽應(yīng)用银还。一個(gè)這樣的應(yīng)用是確定給定的位序列是否設(shè)置了一個(gè)或多個(gè)位。例如洁墙,假設(shè)我們要檢查是否為給定的十進(jìn)制數(shù)設(shè)置了第五位蛹疯。以下是我們?nèi)绾问褂?amp;運(yùn)算符來(lái)執(zhí)行此操作:

首先,創(chuàng)建一個(gè)位掩碼热监,用于檢查目標(biāo)位(在本例中為第五位)是否設(shè)置為1捺弦。位掩碼上的每個(gè)位都設(shè)置為0,但目標(biāo)位置的位除外孝扛,目標(biāo)位置的位設(shè)置為1列吼。二進(jìn)制數(shù)文字可用于輕松實(shí)現(xiàn)這一點(diǎn):

const mask = 0b10000;

接下來(lái),使用十進(jìn)制數(shù)和位掩碼作為操作數(shù)執(zhí)行&操作疗琉,并將結(jié)果與位掩碼進(jìn)行比較冈欢。如果所有目標(biāo)位都設(shè)置為十進(jìn)制數(shù)歉铝,&操作的結(jié)果將等于位掩碼盈简。請(qǐng)注意,位掩碼中的0位將有效地關(guān)閉十進(jìn)制數(shù)中的相應(yīng)位太示,因?yàn)閍&0=0柠贤。

// 34 => 100010
// (34 & mask) => (100010 & 010000) = 000000

console.log((34 & mask) === mask); // false                                                                // 50 => 110010
// (50 & mask) => (110010 & 010000) = 010000

console.log((50 & mask) === mask); // true

奇數(shù)或偶數(shù)

使用&運(yùn)算符檢查十進(jìn)制數(shù)的設(shè)定位可以擴(kuò)展到檢查給定的十進(jìn)制數(shù)是偶數(shù)還是奇數(shù)。為了實(shí)現(xiàn)這一點(diǎn)类缤,使用1作為位掩碼(以確定是否設(shè)置了第一位或最右邊的位)臼勉。

對(duì)于整數(shù),可以使用最低有效位(第一位或最右邊的位)來(lái)確定數(shù)字是偶數(shù)還是奇數(shù)餐弱。如果啟用最低有效位(設(shè)置為1)宴霸,則數(shù)字為奇數(shù)囱晴;否則,數(shù)字為偶數(shù)瓢谢。

function isOdd (int) {

有用的標(biāo)識(shí)

在繼續(xù)下一個(gè)運(yùn)算符之前畸写,這里有一些&操作符的有用標(biāo)識(shí)(對(duì)于任何帶符號(hào)的32位整數(shù)A):

(A & 0) === 0
(A & ~A) === 0
(A & A) === A

(A & -1) === A

按位或(|)

運(yùn)算符對(duì)其操作數(shù)的每對(duì)對(duì)應(yīng)位執(zhí)行“或”運(yùn)算。運(yùn)算符僅當(dāng)兩個(gè)位都為0時(shí)返回0氓扛;否則返回1枯芬。

對(duì)于一對(duì)位,這里是或操作的可能值:

(0 | 0) === 0 (0 | 1) === 1 (1 | 0) === 1 (1 | 1) === 1

‘打開(kāi)’位

在位屏蔽應(yīng)用中采郎,可以使用運(yùn)算符來(lái)確保位序列中的某些位被打開(kāi)(設(shè)置為1)千所。這是基于這樣一個(gè)事實(shí):對(duì)于任何給定的位A:

  • (A | 0 = A) — 和0進(jìn)行或運(yùn)算,位總是會(huì)保持不變蒜埋。

  • (A | 1 = 1) — 和1進(jìn)行或運(yùn)算淫痰,位總是為1。

例如整份,假設(shè)我們有一個(gè)8位整數(shù)黑界,我們希望確保所有偶數(shù)位(第二、第四皂林、第六朗鸠、第八)都打開(kāi)(設(shè)置為1)。| 運(yùn)算符可用于實(shí)現(xiàn)以下目的:

首先础倍,創(chuàng)建一個(gè)位掩碼烛占,其效果是打開(kāi)8位整數(shù)的每個(gè)偶數(shù)位。該位掩碼將是0B101010沟启。請(qǐng)注意忆家,位掩碼的偶數(shù)位設(shè)置為1,而其他位設(shè)置為0德迹。

接下來(lái)芽卿,使用8位整數(shù)和創(chuàng)建的位掩碼執(zhí)行或操作:

const mask = 0b10101010;
// 208 => 11010000
// (208 | mask)
// ------------

// 11010000
// | 10101010
// ------------
// = 11111010
// ------------
// = 250 (decimal)


console.log(208 | mask); // 250

有用的標(biāo)識(shí)

在繼續(xù)下一個(gè)運(yùn)算符之前,這里有一些 | 操作符的有用標(biāo)識(shí)(對(duì)于任何帶符號(hào)的32位整數(shù)A):

(A | 0) === A

按位異或(^)

^運(yùn)算符對(duì)其操作數(shù)的每對(duì)對(duì)應(yīng)位執(zhí)行異或(異或)運(yùn)算胳搞。如果兩個(gè)位相同(0或1)卸例,則^運(yùn)算符返回0;否則肌毅,它返回1筷转。

對(duì)于一對(duì)位,下面是可能的值:

(0 ^ 0) === 0 (0 ^ 1) === 1 (1 ^ 0) === 1 (1 ^ 1) === 0

切換位

在位屏蔽應(yīng)用程序中悬而,^ 運(yùn)算符通常用于切換或翻轉(zhuǎn)位序列中的某些位呜舒。這是基于這樣一個(gè)事實(shí):對(duì)于任何給定的位A:

和0進(jìn)行異或運(yùn)算,位總是會(huì)保持不變笨奠。

(A ^ 0 = A)

當(dāng)與相應(yīng)的1位配對(duì)時(shí)袭蝗,該位總是被切換唤殴。

(A ^ 1 = 1) — if A is 0(A ^ 1 = 0) — if A is 1

例如,假設(shè)我們有一個(gè)8位整數(shù)到腥,我們希望確保除了最低有效位(第一位)和最高有效位(第八位)之外眨八,每個(gè)位都被切換∽蟮纾可以使用^運(yùn)算符實(shí)現(xiàn)以下目的:

首先廉侧,創(chuàng)建一個(gè)位掩碼,其效果是切換8位整數(shù)的每個(gè)位篓足,除了最低有效位和最高有效位段誊。該位掩碼將為0b0111110。請(qǐng)注意栈拖,要切換的位設(shè)置為1连舍,而其他位設(shè)置為0。

接下來(lái)涩哟,使用8位整數(shù)和創(chuàng)建的位掩碼執(zhí)行^操作:

const mask = 0b01111110;
// 208 => 11010000
// (208 ^ mask)
// ------------
// 11010000
// ^ 01111110
// ------------
// = 10101110
// ------------
// = 174 (decimal)


console.log(208 ^ mask); // 174

有用的標(biāo)識(shí)

在繼續(xù)下一個(gè)運(yùn)算符之前索赏,以下是^操作的一些有用標(biāo)識(shí)(對(duì)于任何有符號(hào)的32位整數(shù)A):

(A ^ 0) === A

從上面列出的標(biāo)識(shí)中可以明顯看出,-1上的xor操作等同于a上的按位非操作贴彼。因此潜腻,上面的foundIndex()函數(shù)也可以這樣編寫(xiě):

function foundIndex (index) {

左移(<<)

左移位(<<)運(yùn)算符接受兩個(gè)操作數(shù)。第一個(gè)操作數(shù)是整數(shù)器仗,而第二個(gè)操作數(shù)是要向左移動(dòng)的第一個(gè)操作數(shù)的位數(shù)融涣。零(0)位從右邊移入,而從左邊移入的多余位被丟棄精钮。

例如威鹿,考慮整數(shù)170。假設(shè)我們要向左移動(dòng)三位轨香。我們可以使用<<運(yùn)算符忽你,如下所示:

// 170 => 00000000000000000000000010101010
// 170 << 3
// --------------------------------------------
//    (000)00000000000000000000010101010(***)
// --------------------------------------------
//  = (***)00000000000000000000010101010(000)
// --------------------------------------------
//  = 00000000000000000000010101010000
// --------------------------------------------
//  = 1360 (decimal)


console.log(170 << 3); // 1360

左移位位運(yùn)算符(<<)可以使用以下javascript表達(dá)式定義:

(A << B) => A * (2 ** B) => A * Math.pow(2, B)

因此,回顧前面的示例:

(170 << 3) => 170 * (2 ** 3) => 170 * 8 => 1360

顏色轉(zhuǎn)換:RGB到十六進(jìn)制

左移位(<)運(yùn)算符的一個(gè)非常有用的應(yīng)用程序是將顏色從RGB表示轉(zhuǎn)換為十六進(jìn)制表示臂容。

RGB顏色的每個(gè)組件的顏色值在0-255之間科雳。簡(jiǎn)單地說(shuō),每個(gè)顏色值可以用8位完美地表示策橘。

0 => 0b00000000 (2進(jìn)制) => 0x00 (16進(jìn)制) 255 => 0b11111111 (2進(jìn)制) => 0xff (16進(jìn)制)

因此炸渡,顏色本身可以完美地用24位來(lái)表示(紅色娜亿、綠色和藍(lán)色分量各8位)丽已。從右邊開(kāi)始的前8位表示藍(lán)色分量,接下來(lái)的8位表示綠色分量买决,之后的8位表示紅色分量沛婴。

(binary) => 11111111 00100011 00010100   (red) => 11111111 => ff => 255 (green) => 00100011 => 23 => 35  (blue) => 00010100 => 14 => 20   (hex) => ff2314

既然我們已經(jīng)了解了如何將顏色表示為24位序列氢惋,那么讓我們來(lái)看看如何從顏色的各個(gè)組件的值組成顏色的24位雀哨。假設(shè)我們有一個(gè)用RGB(255、35、20)表示的顏色鳄炉。以下是我們?nèi)绾谓M合這些位:

(red) => 255 => 00000000 00000000 00000000 11111111
(green) =>  35 => 00000000 00000000 00000000 00100011

 (blue) =>  20 => 00000000 00000000 00000000 00010100


// Rearrange the component bits and pad with zeroes as necessary
// Use the left shift operator


  (red << 16) => 00000000 11111111 00000000 00000000
 (green << 8) => 00000000 00000000 00100011 00000000
       (blue) => 00000000 00000000 00000000 00010100

// Combine the component bits together using the OR (|) operator
// ( red << 16 | green << 8 | blue )


      00000000 11111111 00000000 00000000
    | 00000000 00000000 00100011 00000000
    | 00000000 00000000 00000000 00010100
// -----------------------------------------

      00000000 11111111 00100011 00010100
// -----------------------------------------

既然過(guò)程非常清楚,下面是一個(gè)簡(jiǎn)單的函數(shù)称龙,它將顏色的RGB值作為輸入數(shù)組娄周,并基于上述過(guò)程返回顏色的相應(yīng)十六進(jìn)制表示:

function rgbToHex ([red = 0, green = 0, blue = 0] = []) {
  return `#${(red << 16 | green << 8 | blue).toString(16)}`;
}

有符號(hào)右移(>>)

有符號(hào)右移(>>)運(yùn)算符的符號(hào)接受兩個(gè)操作數(shù)。第一個(gè)操作數(shù)是整數(shù)羹奉,而第二個(gè)操作數(shù)是要右移的第一個(gè)操作數(shù)的位數(shù)秒旋。

已移到右邊的多余位將被丟棄,而符號(hào)位(最左邊的位)的副本將從左邊移入诀拭。所以迁筛,整數(shù)的符號(hào)位會(huì)一直保留。所以這種運(yùn)算叫做有符號(hào)右移耕挨。

例如细卧,考慮整數(shù)170和-170。假設(shè)我們想把三位移到右邊筒占。我們可以使用>>運(yùn)算符贪庙,如下所示:

//  170 => 00000000000000000000000010101010
// -170 => 11111111111111111111111101010110
// 170 >> 3
// --------------------------------------------
//    (***)00000000000000000000000010101(010)
// --------------------------------------------
//  = (000)00000000000000000000000010101(***)
// --------------------------------------------
//  = 00000000000000000000000000010101
// --------------------------------------------
//  = 21 (decimal)
// -170 >> 3
// --------------------------------------------
//    (***)11111111111111111111111101010(110)
// --------------------------------------------
//  = (111)11111111111111111111111101010(***)
// --------------------------------------------
//  = 11111111111111111111111111101010
// --------------------------------------------
//  = -22 (decimal)


console.log(170 >> 3); // 21
console.log(-170 >> 3); // -22

通過(guò)以下javascript表達(dá)式可以描述有符號(hào)右移:

(A >> B) => Math.floor(A / (2 ** B)) => Math.floor(A / Math.pow(2, B))

因此,之前的那個(gè)例子可以如下表示:

(170 >> 3) => Math.floor(170 / (2 ** 3)) => Math.floor(170 / 8) => 21(-170 >> 3) => Math.floor(-170 / (2 ** 3)) => Math.floor(-170 / 8) => -22

顏色提取

有符號(hào)右移(>>)運(yùn)算符的一個(gè)非常好的應(yīng)用是從顏色中提取RGB顏色值翰苫。當(dāng)顏色以RGB表示時(shí)插勤,很容易區(qū)分紅色、綠色和藍(lán)色顏色分量值革骨。但是农尖,對(duì)于以十六進(jìn)制表示的顏色,這將花費(fèi)更多的精力良哲。

在上一節(jié)中盛卡,我們看到了從顏色的各個(gè)組成部分(紅色、綠色和藍(lán)色)的位組成顏色的過(guò)程筑凫。如果我們反向執(zhí)行這個(gè)過(guò)程滑沧,我們將能夠提取顏色的各個(gè)組成部分的值。讓我們?cè)囈辉嚒?/p>

假設(shè)我們有一個(gè)用十六進(jìn)制表示法ff2314表示的顏色巍实。下面是顏色的有符號(hào)32位表示:

(color) => ff2314 (hexadecimal) => 11111111 00100011 00010100 (binary) // 32-bit representation of color 00000000 11111111 00100011 00010100

為了獲得單個(gè)部分滓技,我們將根據(jù)需要將顏色位按8的倍數(shù)右移,直到從右邊得到目標(biāo)組件位作為前8位棚潦。由于顏色的32位中的符號(hào)標(biāo)志位是0令漂,因此我們可以安全地使用符號(hào)傳播右移位(>>)運(yùn)算符。

color => 00000000 11111111 00100011 00010100
// Right shift the color bits by multiples of 8
// Until the target component bits are the first 8 bits from the right


  red => color >> 16
      => 00000000 11111111 00100011 00010100 >> 16
      => 00000000 00000000 00000000 11111111

green => color >> 8
      => 00000000 11111111 00100011 00010100 >> 8
      => 00000000 00000000 11111111 00100011

 blue => color >> 0 => color
      => 00000000 11111111 00100011 00010100

現(xiàn)在我們將目標(biāo)顏色位作為右前8位,我們需要一種方法來(lái)屏蔽除前8位之外的所有其他位叠必。這使我們回到和(&)運(yùn)算符荚孵。請(qǐng)記住,&運(yùn)算符可用于確保關(guān)閉某些位纬朝。

讓我們從創(chuàng)建所需的位掩碼開(kāi)始收叶。就像這樣:

mask => 00000000 00000000 00000000 11111111     => 0b11111111 (binary)     => 0xff (hexadecimal)

準(zhǔn)備好位掩碼后,我們可以對(duì)上一次右移操作的每個(gè)結(jié)果執(zhí)行與(&)操作共苛,使用位掩碼提取目標(biāo)顏色判没。

red => color >> 16 & 0xff      =>   00000000 00000000 00000000 11111111      => & 00000000 00000000 00000000 11111111      => = 00000000 00000000 00000000 11111111      =>   255 (decimal)green => color >> 8 & 0xff      =>   00000000 00000000 11111111 00100011      => & 00000000 00000000 00000000 11111111      => = 00000000 00000000 00000000 00100011      =>   35 (decimal) blue => color & 0xff      =>   00000000 11111111 00100011 00010100      => & 00000000 00000000 00000000 11111111      => = 00000000 00000000 00000000 00010100      =>   20 (decimal)

基于上述過(guò)程,這里有一個(gè)簡(jiǎn)單的函數(shù)隅茎,它以十六進(jìn)制顏色字符串(帶有六個(gè)十六進(jìn)制數(shù)字)作為輸入哆致,并返回相應(yīng)的RGB顏色分量值數(shù)組。

function hexToRgb (hex) {  hex = hex.replace(/^#?([0-9a-f]{6})$/i, '$1');  hex = Number(`0x${hex}`);  return [    hex >> 16 & 0xff, // red    hex >> 8 & 0xff,  // green    hex & 0xff        // blue  ];}

無(wú)符號(hào)右移(>>>)

無(wú)符號(hào)右移位(>>>)運(yùn)算符的行為非常類似于符號(hào)傳播右移位(>>)運(yùn)算符患膛。然而摊阀,關(guān)鍵區(qū)別在于從左邊移入的位。

顧名思義踪蹬,0位總是從左邊移入胞此。因此,>>運(yùn)算符始終返回?zé)o符號(hào)32位整數(shù)跃捣,因?yàn)榻Y(jié)果整數(shù)的符號(hào)位始終為0漱牵。對(duì)于正整數(shù),>>和>>>都將始終返回相同的結(jié)果疚漆。

例如酣胀,考慮整數(shù)170和-170。假設(shè)我們要將3位移到右邊娶聘,我們可以使用>>>操作符闻镶,如下所示:

//  170 => 00000000000000000000000010101010
// -170 => 11111111111111111111111101010110
// 170 >>> 3
// --------------------------------------------
//    (***)00000000000000000000000010101(010)
// --------------------------------------------
//  = (000)00000000000000000000000010101(***)
// --------------------------------------------
//  = 00000000000000000000000000010101
// --------------------------------------------
//  = 21 (decimal)
// -170 >>> 3
// --------------------------------------------
//    (***)11111111111111111111111101010(110)
// --------------------------------------------
//  = (000)11111111111111111111111101010(***)
// --------------------------------------------
//  = 00011111111111111111111111101010
// --------------------------------------------
//  = 536870890 (decimal)


console.log(170 >>> 3); // 21
console.log(-170 >>> 3); // 536870890

配置標(biāo)志

在總結(jié)本教程之前,讓我們考慮另一個(gè)非常常見(jiàn)的位操作符和位屏蔽應(yīng)用:配置標(biāo)志丸升。

假設(shè)我們有一個(gè)函數(shù)铆农,它接受幾個(gè)布爾選項(xiàng),這些選項(xiàng)可以用來(lái)控制函數(shù)的運(yùn)行方式或返回的值的類型狡耻。創(chuàng)建此函數(shù)的一種可能方法是將所有選項(xiàng)作為參數(shù)傳遞給該函數(shù)墩剖,可能使用一些默認(rèn)值,例如:

function doSomething (optA = true, optB = true, optC = false, optD = true, ...) {  // something happens here...}

當(dāng)然夷狰,這不太方便岭皂。在以下兩種情況下,這種方法開(kāi)始變得相當(dāng)有問(wèn)題:

  • 假設(shè)我們有10個(gè)以上的布爾選項(xiàng)沼头。我們不能用這么多參數(shù)定義函數(shù)爷绘。

  • 假設(shè)我們只想為第五個(gè)和第九個(gè)選項(xiàng)指定一個(gè)不同的值书劝,并讓其他選項(xiàng)保留默認(rèn)值。我們需要調(diào)用函數(shù)揉阎,將默認(rèn)值作為所有其他選項(xiàng)的參數(shù)傳遞庄撮,同時(shí)為第五個(gè)和第九個(gè)選項(xiàng)傳遞所需的值背捌。

用前面的方法解決問(wèn)題的一種方法是為配置選項(xiàng)使用一個(gè)對(duì)象毙籽,如下所示:

const defaultOptions = {  optA: true,  optB: true,  optC: false,  optD: true,  ...};function doSomething (options = defaultOptions) {  // something happens here...}

這種方法非常優(yōu)雅,您很可能已經(jīng)看到它被使用了毡庆,甚至自己在某個(gè)地方使用過(guò)坑赡。然而,使用這種方法時(shí)么抗,options參數(shù)將始終是一個(gè)對(duì)象毅否,對(duì)于配置選項(xiàng)來(lái)說(shuō),這可以被認(rèn)為是多余的蝇刀。

如果所有選項(xiàng)都采用布爾值螟加,則可以使用整數(shù)而不是對(duì)象來(lái)表示選項(xiàng)。在這種情況下吞琐,整數(shù)的某些位將映射到指定的選項(xiàng)捆探。如果某個(gè)位被打開(kāi)(設(shè)置為1),則指定選項(xiàng)的值為“真”站粟;否則為“假”黍图。

我們可以用一個(gè)簡(jiǎn)單的例子來(lái)演示這種方法。假設(shè)我們有一個(gè)函數(shù)奴烙,它規(guī)范化包含數(shù)字的數(shù)組列表中的項(xiàng)助被,并返回規(guī)范化的數(shù)組。返回的數(shù)組可以由三個(gè)選項(xiàng)控制切诀,即:

  • fraction:將數(shù)組中的每個(gè)項(xiàng)除以數(shù)組中的最大項(xiàng)

  • unique:從數(shù)組中刪除重復(fù)項(xiàng)

  • sorted:將數(shù)組中的項(xiàng)從最低到最高排序

我們可以使用一個(gè)3位整數(shù)來(lái)表示這些選項(xiàng)揩环,每個(gè)位都映射到一個(gè)選項(xiàng)。以下代碼段顯示選項(xiàng)標(biāo)志:

const LIST_FRACTION = 0x1; // (001) const LIST_UNIQUE = 0x2; // (010) const LIST_SORTED = 0x4; // (100)

要激活一個(gè)或多個(gè)選項(xiàng)幅虑,可以根據(jù)需要使用運(yùn)算符組合相應(yīng)的標(biāo)志检盼。例如,我們可以創(chuàng)建一個(gè)標(biāo)志來(lái)激活所有選項(xiàng)翘单,如下所示:

const LIST_ALL = LIST_FRACTION | LIST_UNIQUE | LIST_SORTED; // (111)

同樣吨枉,假設(shè)我們只希望默認(rèn)情況下激活fraction和sorted選項(xiàng)。我們可以再次使用運(yùn)算符哄芜,如下所示:

const LIST_DEFAULT = LIST_FRACTION | LIST_SORTED; // (101)

雖然只使用三個(gè)選項(xiàng)看起來(lái)并不糟糕貌亭,但當(dāng)有這么多選項(xiàng)時(shí),它往往會(huì)變得非橙想混亂圃庭,并且默認(rèn)情況下需要激活其中的許多選項(xiàng)。在這種情況下,更好的方法是使用^運(yùn)算符停用不需要的選項(xiàng):

const LIST_DEFAULT = LIST_ALL ^ LIST_UNIQUE; // (101)

這里剧腻,我們有一個(gè)列表“所有”標(biāo)志拘央,可以激活所有選項(xiàng)。然后书在,我們使用^運(yùn)算符停用唯一選項(xiàng)灰伟,并根據(jù)需要保留其他選項(xiàng)。

現(xiàn)在我們已經(jīng)準(zhǔn)備好了選項(xiàng)標(biāo)志儒旬,可以繼續(xù)定義normalizelist()函數(shù):

function normalizeList (list, flag = LIST_DEFAULT) {  if (flag & LIST_FRACTION) {    const max = Math.max(...list);    list = list.map(value => Number((value / max).toFixed(2)));  }  if (flag & LIST_UNIQUE) {    list = [...new Set(list)];  }  if (flag & LIST_SORTED) {    list = list.sort((a, b) => a - b);  }  return list;}

為了檢查某個(gè)選項(xiàng)是否被激活栏账,我們使用&運(yùn)算符來(lái)檢查該選項(xiàng)的相應(yīng)位是否被打開(kāi)(設(shè)置為1)。&操作是通過(guò)傳遞給函數(shù)的flag參數(shù)和選項(xiàng)的對(duì)應(yīng)標(biāo)志來(lái)執(zhí)行的栈源,如下面的代碼段所示:

// Checking if the unique option is activated
// (flag & LIST_UNIQUE) === LIST_UNIQUE (activated)
// (flag & LIST_UNIQUE) === 0 (deactivated)


flag & LIST_UNIQUE

總結(jié)

嘿挡爵,我真的很高興你能讀完這篇文章,盡管讀了很長(zhǎng)時(shí)間甚垦,但我強(qiáng)烈希望你在讀的時(shí)候?qū)W到一兩件事茶鹃。謝謝你的時(shí)間。

正如我們?cè)诒疚闹兴吹降募枇粒m然使用得很謹(jǐn)慎闭翩,但javascript的位操作符有一些非常有趣的用例。我強(qiáng)烈希望您在閱讀本文的過(guò)程中獲得的見(jiàn)解從現(xiàn)在起用在你的日常開(kāi)發(fā)中垃杖。

【第1705期】JavaScript中按位操作符的有趣應(yīng)用

關(guān)于本文
譯者:@CoyPan
譯文:https://mp.weixin.qq.com/s/Vx0Yu5L2XnhgoDiZFycMkA
作者:@Glad Chinda
原文:https://blog.logrocket.com/interesting-use-cases-for-javascript-bitwise-operators/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末男杈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子调俘,更是在濱河造成了極大的恐慌伶棒,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彩库,死亡現(xiàn)場(chǎng)離奇詭異肤无,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)骇钦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)宛渐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人眯搭,你說(shuō)我怎么就攤上這事窥翩。” “怎么了鳞仙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵寇蚊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我棍好,道長(zhǎng)仗岸,這世上最難降的妖魔是什么允耿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮扒怖,結(jié)果婚禮上较锡,老公的妹妹穿的比我還像新娘。我一直安慰自己盗痒,他們只是感情好蚂蕴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著积糯,像睡著了一般掂墓。 火紅的嫁衣襯著肌膚如雪谦纱。 梳的紋絲不亂的頭發(fā)上看成,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音跨嘉,去河邊找鬼川慌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛祠乃,可吹牛的內(nèi)容都是我干的梦重。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼亮瓷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼琴拧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起嘱支,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蚓胸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后除师,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體沛膳,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年汛聚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锹安。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡倚舀,死狀恐怖叹哭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痕貌,我是刑警寧澤风罩,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站芯侥,受9級(jí)特大地震影響泊交,放射性物質(zhì)發(fā)生泄漏乳讥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一廓俭、第九天 我趴在偏房一處隱蔽的房頂上張望云石。 院中可真熱鬧,春花似錦研乒、人聲如沸汹忠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宽菜。三九已至,卻和暖如春竿报,著一層夾襖步出監(jiān)牢的瞬間铅乡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工烈菌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阵幸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓芽世,卻偏偏與公主長(zhǎng)得像挚赊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子济瓢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 運(yùn)算符是處理數(shù)據(jù)的基本方法荠割,用來(lái)從現(xiàn)有的值得到新的值。JavaScript 提供了多種運(yùn)算符旺矾,本章逐一介紹這些運(yùn)算...
    徵羽kid閱讀 680評(píng)論 0 0
  • 高級(jí)運(yùn)算符 文檔地址 作為 基本運(yùn)算符 的補(bǔ)充蔑鹦,Swift 提供了幾個(gè)高級(jí)運(yùn)算符執(zhí)行對(duì)數(shù)傳值進(jìn)行更加復(fù)雜的操作。這...
    hrscy閱讀 842評(píng)論 0 2
  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,383評(píng)論 0 5
  • 本章將會(huì)介紹 模塊和源文件訪問(wèn)級(jí)別訪問(wèn)控制語(yǔ)法自定義類型子類常量、變量扒吁、屬性火鼻、下標(biāo)構(gòu)造器協(xié)議擴(kuò)展泛型類型別名位運(yùn)算...
    寒橋閱讀 883評(píng)論 0 2
  • 今天終于可以休息會(huì)了,也許是換個(gè)地方雕崩,也許是因?yàn)橛腥舜畎咽挚鳎凑液蜆?lè)爸感覺(jué)是輕松一點(diǎn)了盼铁,而樂(lè)樂(lè)和兩個(gè)姐姐玩的是...
    陽(yáng)光灑灑閱讀 91評(píng)論 0 1