使用二进制设计课堂权限

使用二进制能很方便的表达和计算(组合、切换和校验)权限,以 Linux 文件权限为例。

权限 字母表示 数字表示 二进制
r 4 0b100
w 2 0b010
执行 x 1 0b001

权限之间可组合:

1
2
const 读写 = 0b100 | 0b010 // 6 即 0b110
const 读写执行 = 0b100 | 0b010 | 0b001 // 7 即 0b111

总的组合结果共 C(2,1) * C(2,1) * C(2,1),8 种,无权限 0 (---),读权限 4 (r--),写权限 2 (-w-),执行权限 1 (--x),读和写权限 6 (rw-),读和执行权限 5 (r-x),写和执行权限 3 (-wx),读、写和执行权限 7 (rwx)。

二进制运算符

二进制位运算符包括 |(按位或 OR)、&(按位与 AND)、^(按位异或 XOR)、~(按位非,取反 NOT)、<<(左移 Left shift)、>>(有符号右移)、>>>(无符号右移)。

JavaScript 位运算是基于 32 位整数的,会先把 64 位浮点数转换为 32 位整数计算,计算完成后再将 32 位转为 64 位。

  • | 运算符

| 运算将两个操作数的每个对应位进行或运算,结果中,两个操作数对应位上至少有一个为 1 时才为 1,否则为 0(相当于求并),即 1 | 1 = 10 | 0 = 00 | 1 = 1

1
2
3
4
  1100
| 0010
-------
1110
  • & 运算符

& 运算将两个操作数的每个对应位进行与运算,结果中,两个操作数对应位上都为 1 时才为 1,否则为 0,即 1 & 1 = 10 & 0 = 00 & 1 = 0。以操作数 0010 为例:

1
2
3
4
  xxyx
& 0010
-------
00a0

上述例子中 x 位为任何数,& 运算的结果都是 0,最终结果只受 y 位影响,当 y = 0 时,结果为 0000y = 1 时,结果为 0010。即如果 a & b === btrue,则说明 a 包含 b

  • ^ 运算符

^ 运算将两个操作数的每个对应位进行异或运算,结果中,两个操作数对应位上不相同时才为 1,相同时为 0。利用这个特点,可实现 Toggle 计算。

1
2
3
4
  0001
^ 0010
-------
0011
1
2
3
4
  0011
^ 0010
-------
0001
  • ~ 运算符

~ 将操作数的每一位取反。有点类似于反码,但不同的是,~ 会将符号位也取反,而取反码,符号位不变。

1
2
3
1010
-------
0101

~10 (~0b1010) 为例,其 32 位二进制为 00000000000000000000000000001010 (正数的补码就是原码),~ 取反得到 11111111111111111111111111110101,由于符号位是 1,所以这是一个负数,而计算机中存储负数是以补码的方式来存储的,所以对补码求原码再转成十进制即可,对补码求原码就是使用此补码再求一遍补码,也就是先取反码再补 1,即 100000000000000000000000000010101,结果为 10000000000000000000000000001011,即 -11

~-2 的 32 位二进制为 11111111111111111111111111111110(负数的补码需要反码再加 1),~ 取反得到 00000000000000000000000000000001,再将结果转为十进制,即 1

任何数字 x 的按位非运算结果都是 -(x + 1)。例如,~-5 运算结果为 4

  • << 左移运算符

将数值的二进制码向左移动一定的位(< 32),右边用 0 填充。

1
2
 1010 >> 1
10100

使用左移还可用来取整。位运算操作的是整数,忽略小数部分,等同于数值的整数部分,左移 0 位,结果还是整数部分。

1
2
1.111 << 0 // 1
2.344 << 0 // 2
  • >> 右移运算符

将数值的二进制码向右移动一定的位(<32),遗弃被丢出的位。

1
2
1010 >> 1
101

课堂权限设计

定义课堂黑板权限为 1、语音权限为 2、视频权限为 4。权限间可组合,同时拥有黑板、语音和视频权限 Board | Audio | Video,即 0111。使用左移 << 定义权限、使用按位或 | 组合权限、使用按位异或 ^ 切换权限(添加或删除权限)、使用按位与 & 校验权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// << 定义权限
enum Permission {
None = 0, // 0000
Board = 1 << 0, // 0001,左移 0 位
Audio = 1 << 1, // 0010,左移 1 位
Video = 1 << 2, // 0100,左移 2 位
}
type Student = {
permission: Permission;
}

const student: Student = {
permission: Permission.Board | Permission.Audio // 0011
}

// & 校验权限
// 用户的权限和黑板权限做与运算,结果依然等于黑板权限,则说明用户拥有黑板权限
if ((student.permission & Permission.Board) === Permission.Board) {
console.log('拥有黑板权限')
}
if ((student.permission & Permission.Audio) === Permission.Audio) {
console.log('拥有语音权限')
}
if ((student.permission & Permission.Video) === Permission.Video) {
console.log('拥有视频权限')
}

// ^ 切换权限
student.permission = student.permission ^ Permission.Audio // 如果没有语音权限授予语音权限,如果有则删除

注:如果只是单纯的添加权限,可以使用按位或 |,单纯的删除权限,则可以先取反,再执行与操作 &(~feature)

1
student.permission = student.permission &(~Permission.Audio)