什么是標(biāo)志?標(biāo)志就是顯示事物特征,便于識(shí)別的記號(hào)。在程序中我們通常使用枚舉來表示標(biāo)志,比如權(quán)限標(biāo)志:
// authorityFlag.js
// 是否有查詢權(quán)限
const QUERY = 1
// 是否有更新權(quán)限
const UPDATE = 2
// 是否有刪除權(quán)限
const DELETE = 3
// 是否有新增權(quán)限
const INSERT = 4
以往我們通常是使用數(shù)組來這這類標(biāo)志管理的
// 用戶擁有的權(quán)限
const userAuths = [QUERY, UPDATE, INSERT]
// 是否包含權(quán)限
const containFlag = (auths, [...flags]) =>
flags.every(flag => auths.includes(flag))
// 是否沒有權(quán)限
const noContainFlag = (auths, [...flags]) =>
flags.every(flag => !auths.includes(flag))
// 是否只有某個(gè)權(quán)限
const equalFlag = (auths, flag) =>
flags.length === 1 && flags[0] === flag
// 添加權(quán)限
const addFlag = (auths, [...flags]) => {
flags.forEach(flag => {
if (!containFlag(flag)) {
auths.push(flag)
}
})
}
// 刪除權(quán)限
const removeFlag = (auths, [...flags]) => {
flags.forEach(flag => {
const index = flags.indexOf(flag)
if (index > -1) {
flags.splice(index, 1)
}
})
}
現(xiàn)在我給大家分享使用左移(>>)、按位或(|)、按位與(&)、按位或(|)、按位非(~)四種操作符結(jié)合做標(biāo)志管理的方法
左移(>>)
左移操作符 (<<) 將第一個(gè)操作數(shù)向左移動(dòng)指定位數(shù),左邊超出的位數(shù)將會(huì)被清除,右邊將會(huì)補(bǔ)零。比如:
const numToBinaryString =
num => console.log(num.toString(2))
numToBinaryString(5); // 00000000000000000000000000000101
numToBinaryString(5 << 2); // 00000000000000000000000000010100
numToBinaryString(5 << 3); // 00000000000000000000000000101000
按位或(|)
按位或運(yùn)算符 (|) 其中一個(gè)操作數(shù)或兩個(gè)都是 1,則這個(gè)位上返回 1 ??梢钥醋髅總€(gè)操作數(shù)的||操作符比較好理解,比如:
numToBinaryString(5); // 00000000000000000000000000000101
numToBinaryString(3); // 00000000000000000000000000000011
numToBinaryString(5 | 3); // 00000000000000000000000000000111
按位與(&)
按位與運(yùn)算符 (&) 兩個(gè)操作數(shù)對(duì)應(yīng)的位都是 1,則這個(gè)位上返回 1 ??梢钥醋髅總€(gè)操作數(shù)的&&操作符比較好理解,比如:
numToBinaryString(5); // 00000000000000000000000000000101
numToBinaryString(3); // 00000000000000000000000000000011
numToBinaryString(5 & 3); // 00000000000000000000000000000001
按位非 (~)
按位非運(yùn)算符(~),反轉(zhuǎn)操作數(shù)的位??梢钥醋魉形粩?shù)1 0 反轉(zhuǎn),比如:
numToBinaryString(5); // 00000000000000000000000000000101
numToBinaryString(~5); // 11111111111111111111111111111010
使用 >>、 |、 &、 ~ 做標(biāo)志管理
因?yàn)槿齻€(gè)運(yùn)算符的特殊關(guān)系,所以我們需要在二進(jìn)制數(shù)中,每個(gè)狀態(tài)都只有一個(gè)位數(shù)是1,且不能相同,如下
// before
const QUERY = 1
const UPDATE = 2
const DELETE = 3
const INSERT = 4
// after
const QUERY = 1 // 00000000000000000000000000000001
const UPDATE = 1 << 2 // 00000000000000000000000000000010
const DELETE = 1 << 3 // 00000000000000000000000000000100
const INSERT = 1 << 4 // 00000000000000000000000000001000
那是如何管理這些標(biāo)志的呢,相信你已經(jīng)聰明的你猜到了,
拼合多個(gè)標(biāo)志
因?yàn)?按位或(|) 的特性,我們可以將擁有的標(biāo)志的位數(shù)都聯(lián)合起來
// before
const userAuths = [QUERY, UPDATE, INSERT]
//after
const userAuths = QUERY | UPDATE | INSERT // 00000000000000000000000000001011
是否包含
因?yàn)?按位與(&) 的特性,我們可以單獨(dú)判斷是否擁有某個(gè)標(biāo)志,因?yàn)橹挥袃蓚€(gè)位數(shù)都為1才會(huì)為1,只要結(jié)果不為0即為存在
// before
const containFlag = (auths, [...flags]) =>
flags.every(flag => auths.includes(flag))
//after
const containFlag = (auths, [...flags]) =>
flags.every(flag => auths & flag)
/**
* 比如 auths & QUERY運(yùn)算結(jié)果為
* 00000000000000000000000000001011
* 00000000000000000000000000000001
* =00000000000000000000000000000001
* =1
* 比如 auths & DELETE運(yùn)算結(jié)果為
* 00000000000000000000000000001011
* 00000000000000000000000000000100
* =00000000000000000000000000000000
* =0
* 直接使用更加方便,比如判斷是否有刪除和更新權(quán)限
* if (auths & UPDATE && auths & DELETE)) {
* ...
* }
*/
是否不包含某個(gè)標(biāo)志
// before
const containFlag = (auths, [...flags]) =>
flags.every(flag => auths.includes(flag))
//after
const containFlag = (auths, [...flags]) =>
flags.every(flag => !(auths & flag))
判斷是否只有某個(gè)標(biāo)志
這個(gè)跟平時(shí)一樣,只要使用全等操作符就可以了
// before
const equalFlag = (auths, flag) =>
flags.length === 1 && flags[0] === flag
//after
// 判斷是否只有某個(gè)標(biāo)志直接使用全等即可
const equalFlag = (auths, flag) => auths === flag
/* 直接使用更方便
* if (auths === UPDATE) {
* ...
* }
*/
添加標(biāo)志
其實(shí)添加標(biāo)志與初始化一樣,只需要使用 按位或(|)直接聯(lián)合即可,而且不需要判斷當(dāng)前是否有,因?yàn)榧尤胗械脑捠褂?code>按位或(|)會(huì)保持跟之前一致
// before
const addFlag = (auths, [...flags]) => {
flags.forEach(flag => {
if (!containFlag(flag)) {
auths.push(flag)
}
})
}
// after
const addFlag = (auths, [...flags]) => {
flags.forEach(flag => {
auths |= flag
})
}
/**
* 比如 auths | DELETE運(yùn)算結(jié)果為
* 00000000000000000000000000001011
* 00000000000000000000000000000100
* =00000000000000000000000000001111
* 直接使用更加方便,要添加權(quán)限,z
* auths = auths | DELETE | QUERY
*/
刪除標(biāo)志
刪除標(biāo)志本質(zhì)上是要將要?jiǎng)h除標(biāo)志中1的位數(shù)在變成0,這有些麻煩,因?yàn)闊o論是按位或(|)或者 按位與(&) 都不能滿足,按位或(|)在有這個(gè)標(biāo)志時(shí)不變,在不存在這個(gè)標(biāo)志時(shí)會(huì)添加,按位與(&)也不行,在沒有這個(gè)標(biāo)志時(shí)會(huì)全部清除,在有這個(gè)標(biāo)志時(shí)會(huì)僅剩這個(gè)標(biāo)志.這時(shí)候我們就要引入上文說的按位非 (~)運(yùn)算符了,我們可以先將要?jiǎng)h除的標(biāo)志取按位非,這時(shí)候除了表示標(biāo)志的位數(shù)為0其他都為1,這時(shí)候我們按位與(&)聯(lián)合這個(gè)處理后的數(shù)字,就能得到我們想要的效果了
// before
const removeFlag = (auths, [...flags]) => {
flags.forEach(flag => {
const index = auths.indexOf(flag)
if (index > -1) {
flags.splice(index, 1)
}
})
}
// after
const removeFlag = (auths, [...flags]) => {
flags.forEach(flag => {
auths &= ~flag
})
}
/**
* 比如 auths &= ~DELETE運(yùn)算結(jié)果為
* UPDATE 00000000000000000000000000000010
* ~UPDATE 11111111111111111111111111111101
* auths 00000000000000000000000000001011
* auths &= ~UPDATE 00000000000000000000000000001001
* 直接使用更加方便,要?jiǎng)h除的權(quán)限,
* auths & ~DELETE
*/
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):相對(duì)于數(shù)組來說使用位運(yùn)算符的速度要快得多,特別是狀態(tài)比較多,或者使用比較頻繁的情況下,而且對(duì)于熟悉位運(yùn)算符的人來說,更加直觀.
缺點(diǎn):正如優(yōu)點(diǎn)所對(duì)應(yīng)的一樣,對(duì)于不熟悉位運(yùn)算符的開發(fā)同學(xué),這一定是魔鬼寫法,非常不友好.而且左移(>>)運(yùn)算符最多只支持32位左移,比如numToBinaryString(1 << 30)結(jié)果就到達(dá) 1000000000000000000000000000000了,所以這種管理標(biāo)志方法最多只能支持31中標(biāo)志,不過已經(jīng)足夠了,因?yàn)榇蟛糠謶?yīng)用是不超過31種標(biāo)志的.