引言
如果你是一個(gè)硬件工程師洋侨,那么你對位運(yùn)算相關(guān)的東西必然已經(jīng)非常熟悉,但作為一個(gè)前端工程師的你呢倦蚪?這一點(diǎn)上希坚,確實(shí)比較讓人疑惑。當(dāng)我在學(xué)校里陵且,還在搞硬件相關(guān)的東西的時(shí)候裁僧,一方面的原因是當(dāng)初的應(yīng)用場景很多時(shí)候都會(huì)接觸到,而另一方面慕购,當(dāng)初使用的編程語言并沒有如同 JavaScript 一般靈活方便聊疲。考慮到 ROM/RAM 等因素沪悲,我如果想要依次點(diǎn)亮一排流水燈的時(shí)候获洲,我可能會(huì)使用位移運(yùn)算符配合一個(gè)數(shù)字來標(biāo)記哪顆燈應(yīng)該被點(diǎn)亮;當(dāng)我需要存儲多個(gè)布爾類型的數(shù)據(jù)的時(shí)候殿如,我可能使用掩碼的形式贡珊,存儲和使用需要的信息。但是當(dāng)我習(xí)慣了 JavaScript 提供給我的便利以及運(yùn)行環(huán)境提供給我更大的容量涉馁、更快的計(jì)算速度的時(shí)候门岔,不知不覺間,我竟?jié)u漸忘了之前的編程方式烤送。所以寒随,不如提醒一下自己。
二進(jìn)制與位運(yùn)算
雖然現(xiàn)代瀏覽器帮坚、Nodejs 等為開發(fā)者提供了強(qiáng)大的 JavaScript 運(yùn)行環(huán)境妻往,但是這些環(huán)境的核心仍舊是運(yùn)行在二進(jìn)制的基礎(chǔ)之上的,毋庸置疑的是试和,基于二進(jìn)制數(shù)據(jù)在運(yùn)行位運(yùn)算的時(shí)候遠(yuǎn)比其它數(shù)學(xué)運(yùn)算讯泣、布爾操作等快得多。JavaScript 為開發(fā)者提供了多種位運(yùn)算支持:
- 按位與(AND灰署,&)
當(dāng)兩個(gè)操作數(shù)對應(yīng)位數(shù)都是 1 時(shí)判帮,則在該位返回 1局嘁,否則則在該位返回 0溉箕;
1 & 4 // 0, 1 & 100 -> 000
3 & 5 // 1, 11 & 101 -> 001
- 按位或(OR晦墙,|)
當(dāng)兩個(gè)操作數(shù)對應(yīng)位數(shù)中至少有一個(gè)是 1 是,則在該位返回 1肴茄,否則則在該位返回 0晌畅;
1 | 4 // 5, 1 | 100 -> 101
3 | 5 // 7, 11 | 101 -> 111
- 按位異或(XOR,^)
當(dāng)兩個(gè)操作數(shù)對應(yīng)位數(shù)中兩個(gè)數(shù)中寡痰,一個(gè)為 1抗楔,一個(gè)為 00,則在該位返回 1拦坠,否則則在該位返回连躏;
1 ^ 4 // 5, 1 ^ 100 -> 101
3 ^ 5 // 6, 11 ^ 101 -> 110
- 按位取反(NOT,~)
在操作數(shù)對應(yīng)位置贞滨,是 1 則在該位返回 0入热,否則則在該位返回 1;
~1 // -2, 因?yàn)檠a(bǔ)碼存在的原因晓铆,這里可能和期望的有所不同勺良,有興趣可以深入學(xué)習(xí)一下
- 移位運(yùn)算 (>>, <<, >>>, <<<)
5 >> 1 // 2, 101 >> 1 -> 010
3 << 1 // 6, 11 << 1 -> 110
......
利用位運(yùn)算進(jìn)行性能優(yōu)化
(一) 代替數(shù)學(xué)運(yùn)算
很多情況下,我們可以使用位運(yùn)算替代數(shù)學(xué)運(yùn)算操作骄噪,比如判斷一個(gè)數(shù)是否是奇數(shù)尚困,最典型的做法當(dāng)然是對 2 求余數(shù),也即 if (value % 2) {}
链蕊。但與此同時(shí)事甜,二進(jìn)制奇數(shù)的最低位一定是 1,這樣我們就能通過如按位與的方式完成我們的需求滔韵,if (value & 1) {}
讳侨。雖然改動(dòng)比較小,但這個(gè)計(jì)算速度卻將因此得到很大的提高奏属。
(二) 位掩碼
位掩碼在一些偏底層的語言中使用得非常廣泛跨跨,但 JavaScript 中卻似乎被大眾給忽略了,更多的情況下囱皿,大家都在關(guān)注業(yè)務(wù)勇婴、功能和兼容性等方面的問題,運(yùn)行環(huán)境中提供的便利的 api 似乎也讓我們覺得沒太大必要使用掩碼技術(shù)嘱腥,但性能要求較高的場景中耕渴,我們還是會(huì)嘗試從一些經(jīng)典的編程范式中尋找答案。用一個(gè)很簡單的例子來說明位掩碼技術(shù):
// 定義所有的可選項(xiàng)
var OPTION_A = 1,
OPTION_B = 2,
OPTION_C = 4,
OPTION_D = 8,
OPTION_E = 16;
// 包含選項(xiàng) A齿兔、B橱脸、D
var options = OPTION_A | OPTION_B | OPTION_D; // 1 | 10 | 1000 -> 1011
// 判斷選項(xiàng)是否被包含在 options 中
options & OPTION_A // 1011 & 1 -> 0001, true
options & OPTION_B // 1011 & 10 -> 0010, true
options & OPTION_C // 1011 & 110 -> 0, false
options & OPTION_D // 1011 & 1000 -> 1000, true
options & OPTION_E // 1011 & 10000 -> 0, false
之前础米,你可能會(huì)使用比如數(shù)組的 indexOf 或者 Map 數(shù)據(jù)結(jié)構(gòu)等方式來確認(rèn)一個(gè)數(shù)據(jù)在不在你的數(shù)據(jù)集合中,但相比而言添诉,掩碼的運(yùn)算速度遠(yuǎn)比它們高屁桑,除卻函數(shù)調(diào)用帶來額外開銷,位運(yùn)算發(fā)生于系統(tǒng)底層栏赴,尤其是如果有多個(gè)選項(xiàng)保存在一起并需要頻繁檢查的時(shí)候蘑斧,位掩碼將大放異彩。
位運(yùn)算的作用遠(yuǎn)不止如此须眷,我們整個(gè)計(jì)算機(jī)系統(tǒng)都是在其基礎(chǔ)之上構(gòu)建出來的竖瘾,這里羅列的只是冰山一角,歡迎大家?guī)臀已a(bǔ)充~~~