不管是前端還是后端的伙伴,在工作中會(huì)經(jīng)常遇到權(quán)限控制的場景,業(yè)務(wù)上無非就幾種權(quán)限:
頁面權(quán)限、操作權(quán)限、數(shù)據(jù)權(quán)限,不同公司根據(jù)業(yè)務(wù)需要都采取不同的方法區(qū)控制權(quán)限,我們這里討論一下使用JavaScript中的位運(yùn)算符來控制權(quán)限。
進(jìn)制類型
JavaScript 中提供的進(jìn)制表示方法有四種:十進(jìn)制、二進(jìn)制、十六進(jìn)制、八進(jìn)制。
對于數(shù)值字面量,主要使用不同的前綴來區(qū)分:
- 十進(jìn)制:取值數(shù)字 0-9;不用前綴。
- 二進(jìn)制(Binary): 取值數(shù)字 0 和 1 ;前綴 0b 或 0B。
- 十六進(jìn)制(Hexadecimal):取值數(shù)字 0-9 和 a-f ;前綴 0x 或 0X。
- 八進(jìn)制(Octal):取值數(shù)字 0-7 ;前綴 0o 或 0O (ES6規(guī)定)。
位運(yùn)算符
什么是位運(yùn)算符?
位運(yùn)算符指的是二進(jìn)制位的運(yùn)算,先將十進(jìn)制數(shù)轉(zhuǎn)成二進(jìn)制后再進(jìn)行運(yùn)算。 在二進(jìn)制位運(yùn)算中,1表示true,0表示false。
JavaScript 中的按位操作符有:
| 運(yùn)算符 | 用法 | 描述 |
|---|---|---|
| 按位與(AND) | A & B | 如果對應(yīng)的二進(jìn)制位都為 1,則該二進(jìn)制位為 1 |
| 按位或(OR) | A 或 B | 如果對應(yīng)的二進(jìn)制位有一個(gè)為 1,則該二進(jìn)制位為 1 |
| 按位異或(XOR) | A ^ B | 如果對應(yīng)的二進(jìn)制位只有一個(gè)為 1,則該二進(jìn)制位為 1 |
| 按位非(NOT) | ~A | 反轉(zhuǎn)所有二進(jìn)制位,即 1 轉(zhuǎn)換為 0,0 轉(zhuǎn)換為 1 |
| 按位左移 | A << B | 將所有二進(jìn)制位統(tǒng)一向左移動(dòng)指定的位數(shù),并在最右側(cè)補(bǔ) 0 |
| 按位右移 | A >> B | 按位右移(有符號右移):將所有二進(jìn)制位統(tǒng)一向右移動(dòng)指定的位數(shù),并拷貝最左側(cè)的位來填充左側(cè) |
| 無符號右移 | A >>> B | 按位右移零(無符號右移):將所有二進(jìn)制位統(tǒng)一向右移動(dòng)指定的位數(shù),并在最左側(cè)補(bǔ) 0 |
示例:
const A = 0101,B = 0001
// 按位與(AND)
A & B = 0001
// 按位或(OR)
A | B = 0101
// 按位異或(XOR)
A ^ B = 0100
// 按位非(NOT)
~A = 1010
// 按位左移
A << 1 = 1010
// 按位右移
A >> 1 = 0010
// 無符號右移
A >>> 1 = 0010
位運(yùn)算符在工作中的應(yīng)用得比較少,但有時(shí)候它可以很巧妙地解決我們工作中一些問題。
運(yùn)用場景
在傳統(tǒng)的權(quán)限系統(tǒng)中,不同的權(quán)限之間存在很多關(guān)聯(lián)關(guān)系,而且有很多種權(quán)限組合方式,在這種情況下,權(quán)限就越難以維護(hù)。這種情況我們就可以使用位運(yùn)算符,可以很巧妙地解決這個(gè)問題。
假設(shè)我們現(xiàn)在權(quán)限系統(tǒng)中有4種基本權(quán)限:可讀、可寫、創(chuàng)建、刪除。
那么我們可以定義4個(gè)二進(jìn)制變量表示:
// 所有權(quán)限碼的二進(jìn)制數(shù)形式,有且只有一位值為 1,其余全部為 0
const READ = 0b1000 // 可讀
const WRITE = 0b0100 // 可寫
const CREATE = 0b0010 // 創(chuàng)建
const DELETE = 0b0001 // 刪除
權(quán)限操作
- 使用
按位或(OR)添加權(quán)限:
// 賦予用戶全部權(quán)限
const ALL = READ | WRITE | CREATE | DELETE
console.log(ALL)
// 結(jié)果位 1111,每個(gè)位置的1就代表擁有這個(gè)權(quán)限,這里全部是1,就代表擁有全部權(quán)限。
// 同樣的,這些權(quán)限可以自由組合
const READ_AND_WRITE = READ | WRITE // 可讀和可寫,結(jié)果為 1100
const READ_AND_CREATE = READ | CREATE // 可讀和創(chuàng)建,結(jié)果為 1010
const WRITE_AND_DELETE = WRITE | DELETE // 可寫和刪除,結(jié)果為 0101
- 使用
按位與(AND)校驗(yàn)權(quán)限:
// 比如我們拿到一個(gè)用戶的權(quán)限,我們怎么根據(jù)返回的數(shù)據(jù)判斷是否擁有某個(gè)權(quán)限呢?
// 假設(shè)現(xiàn)在返回了 擁有可讀可寫的權(quán)限組合:1100
const auth = READ | WRITE // 可讀和可寫,結(jié)果為 1100
// 判斷是否包含 READ 權(quán)限
const isRead = (auth & READ) === READ // true
// 是否包含 DELETE 權(quán)限
const isDelete = (auth & DELETE) === DELETE // false
- 使用
按位非(NOT)剔除權(quán)限:
// 全部權(quán)限
const ALL = READ | WRITE | CREATE | DELETE
// 如果要剔除 WRITE 權(quán)限,應(yīng)該怎么做呢,先執(zhí)行 ~ 取反,再執(zhí)行 & 運(yùn)算
const notWrite = ALL & ~WRITE // 輸出 1011
// 剔除 DELETE 權(quán)限
const notDelete = ALL & ~DELETE // 輸出 1110
局限性
本文提到的這種位運(yùn)算符方案,有一定的前提條件:
- 每種權(quán)限碼都是唯一的,有且只有一位值為 1。
- 一個(gè)數(shù)字的范圍只能在 -(2^53 -1) 和 2^53 -1 之間,如果權(quán)限系統(tǒng)設(shè)計(jì)得比較龐大,這種方式可能不合適。
不過總的來說,這種方式在中小型業(yè)務(wù)中應(yīng)該夠用了。