JavaScript 中的位运算和权限设计

1. 内容概要本文主要讨论以下两个问题:

  • JAVAScript 的位运算:先简单回顾下位运算,平时用的少,相信不少人和我一样忘的差不多了
  • 权限设计:根据位运算的特点,设计一个权限系统(添加、删除、判断等)
2. JavaScript 位运算2.1. Number
在讲位运算之前,首先简单看下 JavaScript 中的 Number,下文需要用到 。
在 JavaScript 里,数字均为基于 IEEE 754 标准的双精度 64 位的浮点数,引用维基百科的图片,它的结构长这样:
JavaScript 中的位运算和权限设计

文章插图
 
  • sign bit(符号): 用来表示正负号
  • exponent(指数): 用来表示次方数
  • mantissa(尾数): 用来表示精确度
也就是说一个数字的范围只能在 -(2^53 -1) 至 2^53 -1 之间 。
既然讲到这里,就多说一句:0.1 + 0.2 算不准的原因也在于此 。浮点数用二进制表达时是无穷的,且最多 53 位,必须截断,进而产生误差 。最简单的解决办法就是放大一定倍数变成整数,计算完成后再缩小 。不过更稳妥的办法是使用下文将会提到的 math.js 等工具库 。
此外还有四种数字进制:
// 十进制1234567890// 二进制:前缀 0b,0B0b10000000000000000000000000000000 // 21474836480b01111111100000000000000000000000 // 21390950400B00000000011111111111111111111111 // 8388607// 八进制:前缀 0o,0O(以前支持前缀 0)0o755 // 4930o644 // 420// 十六进制:前缀 0x,0X0xFFFFFFFFFFFFFFFFF // 2951479051793528300000x123456789ABCDEF // 819855292164869000XA // 10好了,Number 就说这么多,接下来看 JavaScript 中的位运算 。
2.2. 位运算
按位操作符将其操作数当作 32 位的比特序列(由 0 和 1 组成)操作,返回值依然是标准的 JavaScript 数值 。JavaScript 中的按位操作符有:
JavaScript 中的位运算和权限设计

文章插图
 
下面举几个例子,主要看下 AND 和 OR:
# 例子1 A = 10001001 B = 10010000A | B = 10011001# 例子2 A = 10001001 C = 10001000A | C = 10001001# 例子1 A = 10001001 B = 10010000A & B = 10000000# 例子2 A = 10001001 C = 10001000A & C = 100010003. 位运算在权限系统中的使用传统的权限系统里,存在很多关联关系,如用户和权限的关联,用户和角色的关联 。系统越大,关联关系越多,越难以维护 。而引入位运算,可以巧妙的解决该问题 。
在讲“位运算在权限系统中的使用”之前,我们先假定两个前提,下文所有的讨论都是基于这两个前提的:
  1. 每种权限码都是唯一的(这是显然的)
  2. 所有权限码的二进制数形式,有且只有一位值为 1,其余全部为 0(2^n)
如果用户权限和权限码,全部使用二级制数字表示,再结合上面 AND 和 OR 的例子,分析位运算的特点,不难发现:
  • | 可以用来赋予权限
  • & 可以用来校验权限
为了讲的更明白,这里用 linux 中的实例分析下,Linux 的文件权限分为读、写和执行,有字母和数字等多种表现形式:
权限 字母表示 数字表示 二进制 读 r 4 0b100 写 w 2 0b010 执行 x 1 0b001
可以看到,权限用 1、2、4(也就是 2^n)表示,转换为二进制后,都是只有一位是 1,其余为 0 。我们通过几个例子看下,如何利用二进制的特点执行权限的添加,校验和删除 。
3.1. 添加权限
let r = 0b100let w = 0b010let x = 0b001// 给用户赋全部权限(使用前面讲的 | 操作)let user = r | w | xconsole.log(user)// 7console.log(user.toString(2))// 111// r = 0b100// w = 0b010// r = 0b001// r|w|x = 0b111可以看到,执行 r | w | x 后,user 的三位都是 1,表明拥有了全部三个权限 。
Linux 下出现权限问题时,最粗暴的解决方案就是 chmod 777 xxx,这里的 7 就代表了:可读,可写,可执行 。而三个 7 分别代表:文件所有者,文件所有者所在组,所有其他用户 。
3.2. 校验权限
刚才演示了权限的添加,下面演示权限校验:
let r = 0b100let w = 0b010let x = 0b001// 给用户赋 r w 两个权限let user = r | w// user = 6// user = 0b110 (二进制)console.log((user & r) === r) // true 有 r 权限console.log((user & w) === w) // true 有 w 权限console.log((user & x) === x) // false 没有 x 权限如前所料,通过 用户权限 & 权限 code === 权限 code 就可以判断出用户是否拥有该权限 。
3.3. 删除权限
我们讲了用 | 赋予权限,使用 & 判断权限,那么删除权限呢?删除权限的本质其实是将指定位置上的 1 重置为 0 。上个例子里用户权限是 0b110,拥有读和写两个权限,现在想删除读的权限,本质上就是将第三位的 1 重置为 0,变为 0b010:


推荐阅读