前言
今日早讀文章由騰訊@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/