Js中的位运算和权限设计10bet:

来源:http://www.chinese-glasses.com 作者:Web前端 人气:60 发布时间:2020-03-14
摘要:时间: 2019-11-20阅读: 98标签: 位运算1. 内容概要 时间: 2019-06-20阅读: 266标签: 运算什么是位运算? 本文主要讨论以下两个问题: 位运算是在数字底层(即表示数字的 32个数位)进行运算的

时间: 2019-11-20阅读: 98标签: 位运算1. 内容概要

时间: 2019-06-20阅读: 266标签: 运算什么是位运算?

本文主要讨论以下两个问题:

位运算是在数字底层(即表示数字的 32 个数位)进行运算的。由于位运算是低级的运算操作,所以速度往往也是最快的(相对其它运算如加减乘除来说),并且借助位运算有时我们还能实现更简单的程序逻辑,缺点是很不直观,许多场合不能够使用。

JavaScript 的位运算:先简单回顾下位运算,平时用的少,相信不少人和我一样忘的差不多了权限设计:根据位运算的特点,设计一个权限系统(添加、删除、判断等)2. JavaScript 位运算2.1. Number

位运算只对整数起作用,如果一个运算子不是整数,会自动转为整数后再运行。虽然在 JavaScript 内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。关于二进制

在讲位运算之前,首先简单看下 JavaScript 中的 Number,下文需要用到。

ECMAScript 整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数)。在 ECMAScript 中,所有整数字面量默认都是有符号整数,这意味着什么呢?

在 JavaScript 里,数字均为基于 IEEE 754 标准的双精度 64 位的浮点数,引用维基百科的图片,它的结构长这样:

有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数。数值范围从 -2147483648 到 2147483647。

sign bit(符号): 用来表示正负号exponent(指数): 用来表示次方数mantissa(尾数): 用来表示精确度

可以以两种不同的方式存储二进制形式的有符号整数,一种用于存储正数,一种用于存储负数。正数是以真二进制形式存储的,前 31 位中的每一位都表示 2 的幂,从第 1 位(位 0)开始,表示 20,第 2 位(位 1)表示 21。没用到的位用 0 填充,即忽略不计。例如,下图展示的是数 18 的表示法。

也就是说一个数字的范围只能在 -(2^53 -1) 至 2^53 -1 之间。

那在js中二进制和十进制如何转换呢?如下

既然讲到这里,就多说一句:0.1 + 0.2 算不准的原因也在于此。浮点数用二进制表达时是无穷的,且最多 53 位,必须截断,进而产生误差。最简单的解决办法就是放大一定倍数变成整数,计算完成后再缩小。不过更稳妥的办法是使用下文将会提到的 math.js 等工具库。

// 十进制 = 二进制let num = 10;console.log(num.toString(2));// 二进制 = 十进制let num1 = 1001;console.log(parseInt(num1, 2)); 

此外还有四种数字进制:

js中都有哪些位运算?按位或 |

// 十进制1234567890// 二进制:前缀 0b,0B0b10000000000000000000000000000000 // 21474836480b01111111100000000000000000000000 // 21390950400B00000000011111111111111111111111 // 8388607// 八进制:前缀 0o,0O(以前支持前缀 0)0o755 // 4930o644 // 420// 十六进制:前缀 0x,0X0xFFFFFFFFFFFFFFFFF // 2951479051793528300000x123456789ABCDEF // 819855292164869000XA // 10

对每对比特位执行与(AND)操作。只有 a 和 b 任意一位为1时,a | b 就是 1。如下表9 | 3 = 11

好了,Number 就说这么多,接下来看 JavaScript 中的位运算。

9

2.2. 位运算

=

按位操作符将其操作数当作 32 位的比特序列(由 0 和 1 组成)操作,返回值依然是标准的 JavaScript 数值。JavaScript 中的按位操作符有:

1

运算符

0

用法

0

描述

1

按位与(AND)a b对于每一个比特位,只有两个操作数相应的比特位都是 1 时,结果才为 1,否则为 0。按位或(OR)a | b对于每一个比特位,当两个操作数相应的比特位至少有一个 1 时,结果为 1,否则为 0。按位异或(XOR)a ^ b对于每一个比特位,当两个操作数相应的比特位有且只有一个 1 时,结果为 1,否则为 0。按位非(NOT)~a反转操作数的比特位,即 0 变成 1,1 变成 0。左移(Left shift)a b将 a 的二进制形式向左移 b ( 32) 比特位,右边用 0 填充。有符号右移a b将 a 的二进制表示向右移 b ( 32) 位,丢弃被移出的位。无符号右移a b将 a 的二进制表示向右移 b ( 32) 位,丢弃被移出的位,并使用 0 在左侧填充。

3=001111=1011

下面举几个例子,主要看下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 = 10001000

10bet,取整

  1. 位运算在权限系统中的使用

对于一般的整数,返回值不会有任何变化。对于大于2的32次方的整数,大于32位的数位都会被舍去。

传统的权限系统里,存在很多关联关系,如用户和权限的关联,用户和角色的关联。系统越大,关联关系越多,越难以维护。而引入位运算,可以巧妙的解决该问题。

function toInt(num) { return num | 0}console.log(toInt(1.8)) // 1console.log(toInt(1.23232)) // 1

在讲“位运算在权限系统中的使用”之前,我们先假定两个前提,下文所有的讨论都是基于这两个前提的

边界判断

每种权限码都是唯一的(这是显然的)所有权限码的二进制数形式,有且只有一位值为 1,其余全部为 0(2^n)

假如我们有一个拖动事件,规定被拖动模块需要在容器内部运动,这时就有边界判断,这其中又包括上,下,左,右四种单一边界,同时还有类似上右,上左等叠加边界,如果我们需要记录这种状态,通过位运算要比使用if判断要简单一些,上右下左四种边界分别用1,2,4,8表示,代码如下:

如果用户权限和权限码,全部使用二级制数字表示,再结合上面AND和OR的例子,分析位运算的特点,不难发现:

let flag = 0;if (pos.left  left) flag = flag | 8;if (pos.right  right) flag = flag | 2;if (pos.bottom  bottom) flag = flag | 4;if (pos.top  top) flag = flag | 1;switch(flag) { // 上 case 1: // 右 case 2: // 右上 case 3: // 下 case 4: // 右下 case 6: // 左 case 8: // 左上 case 9: // 左下 case 12: // code}

|可以用来赋予权限可以用来校验权限

同理,假如我们有一系列控制开关,通过 a | b | c的形式要比 '{a: true, b: true, c: true}' 简单的多。按位与 对每对比特位执行与(AND)操作。只有 a 和 b 都为1时,a b 就是 1。如下表9 3 = 1

为了讲的更明白,这里用 Linux 中的实例分析下,Linux 的文件权限分为读、写和执行,有字母和数字等多种表现形式:

9

权限

=

字母表示

1

数字表示

0

二进制

0

读r40b100写w20b010执行x10b001

1

可以看到,权限用 1、2、4(也就是2^n)表示,转换为二进制后,都是只有一位是 1,其余为 0。我们通过几个例子看下,如何利用二进制的特点执行权限的添加,校验和删除。

3=00111=0001

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,表明拥有了全部三个权限。

我们知道奇数的二进制最后一位必然为1,所以任意一个奇数 1 一定等于1。

Linux 下出现权限问题时,最粗暴的解决方案就是 chmod 777 xxx,这里的 7就代表了:可读,可写,可执行。而三个 7 分别代表:文件所有者,文件所有者所在组,所有其他用户。3.2. 校验权限

// 判断奇偶return number  1 === 1

刚才演示了权限的添加,下面演示权限校验:

系统权限

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 权限

业务场景:我们假设某个管理系统有a, b, c, d四级权限,其中不同帐号分别有不同的权限(可能有1个或多个),例如admin 账户有a + b +c +d 四级权限,guest用户有b + c权限,那这时候应该怎么设计更简单一些呢?

如前所料,通过用户权限 权限 code === 权限 code就可以判断出用户是否拥有该权限。

按位与:是时候登场了!

3.3. 删除权限

基本思路:我们把权限分别用0001, 0010, 0100, 1000表示(即最通俗的1,2,4,8),如果admin用户有a, b, c, d四种权限,则admin的权限为 1 | 2 | 4 | 8 = 15,而guest用户权限为 4 | 8 = 12, 则判断用户是否有某种权限可以如下判断

我们讲了用|赋予权限,使用判断权限,那么删除权限呢?删除权限的本质其实是将指定位置上的 1 重置为 0。上个例子里用户权限是0b110,拥有读和写两个权限,现在想删除读的权限,本质上就是将第三位的 1 重置为 0,变为0b010:

admin  4 === 4admin  8 === 8admin  2 === 2admin  1 === 1
let r = 0b100let w = 0b010let x = 0b001let user = 0b010;console.log((user  r) === r) // false 没有 r 权限console.log((user  w) === w) // true 有 w 权限console.log((user  x) === x) // false 没有 x 权限

按位异或 ^

那么具体怎么操作呢?其实有两种方案,最简单的就是异或^,按照上文的介绍“当两个操作数相应的比特位有且只有一个 1 时,结果为 1,否则为 0”,所以异或其实是 toggle 操作,无则增,有则减:

对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。

let r = 0b100let w = 0b010let x = 0b001let user = 0b110 // 有 r w 两个权限// 执行异或操作,删除 r 权限user = user ^ rconsole.log((user  r) === r) // false 没有 r 权限console.log((user  w) === w) // true 有 w 权限console.log((user  x) === x) // false 没有 x 权限console.log(user.toString(2)) // 现在 user 是 0b010// 再执行一次异或操作user = user ^ rconsole.log((user  r) === r) // true 有 r 权限console.log((user  w) === w) // true 有 w 权限console.log((user  x) === x) // false 没有 x 权限console.log(user.toString(2)) // 现在 user 又变回 0b110

==其运算法则相当于不带进位的二进制加法==

那么如果单纯的想删除权限(而不是无则增,有则减)怎么办呢?答案是执行(~code),先取反,再执行与操作:

9

let r = 0b100let w = 0b010let x = 0b001let user = 0b110 // 有 r w 两个权限// 删除 r 权限user = user  (~r)console.log((user  r) === r) // false 没有 r 权限console.log((user  w) === w) // true 有 w 权限console.log((user  x) === x) // false 没有 x 权限console.log(user.toString(2)) // 现在 user 是 0b010// 再执行一次user = user  (~r)console.log((user  r) === r) // false 没有 r 权限console.log((user  w) === w) // true 有 w 权限console.log((user  x) === x) // false 没有 x 权限console.log(user.toString(2)) // 现在 user 还是 0b010,并不会新增

=

  1. 局限性和解决办法

1

前面我们回顾了 JavaScript 中的 Number 和位运算,并且了解了基于位运算的权限系统原理和 Linux 文件系统权限的实例。

0

上述的所有都有前提条件:1、每种权限码都是唯一的;2、每个权限码的二进制数形式,有且只有一位值为 1(2^n)。也就是说,权限码只能是 1, 2, 4, 8,...,1024,...而上文提到,一个数字的范围只能在 -(2^53 -1) 和 2^53 -1 之间,JavaScript 的按位操作符又是将其操作数当作32 位比特序列的。那么同一个应用下可用的权限数就非常有限了。这也是该方案的局限性。

0

本文由10bet发布于Web前端,转载请注明出处:Js中的位运算和权限设计10bet:

关键词:

最火资讯