给大家看一个非常有趣的代码。这是 React 源码的一部分。这源码里边,它喜欢这么一些"乱七八糟"的数字,而且你会发现一个数字前边它有一个 0b
。
看到那个 0b
我们都知道,它是一个在 JS 里边二进制的表示方式。
js
// ReactFiberFlags.js
export const NoFlags = /* */ 0b0000000000000000000000000000;
export const PerformedWork = /* */ 0b0000000000000000000000000001;
export const Placement = /* */ 0b0000000000000000000000000010;
export const DidCapture = /* */ 0b0000000000000000000000000100;
export const Hydrating = /* */ 0b0000000000000000000000001000;
// You can change the rest (and add more).
export const Update = /* */ 0b0000000000000000000000010000;
export const ChildDeletion = /* */ 0b0000000000000000000000100000;
export const ContentReset = /* */ 0b0000000000000000000001000000;
export const Callback = /* */ 0b0000000000000000000010000000;
export const Snapshot = /* */ 0b0000000000000000000100000000;
它为什么要用二进制表示的数字在里边呢?这里呢,我举个例子你就能理解了。
比方说,我们要在我们的项目里边要去记录一些权限。比如有这么一个用户,或者是某个角色,对某一篇文章具有什么样的权限呢?你要去记录它呗。那么记录的时候呢,我们可能会这样想:我用一个数字来表示各种基本的权限。比方说,表示"可读"我用一个数字 1 来表示,然后呢"可写"呢我用一个数字 2,然后呢"可分享"呢用一个数字 3,然后呢"可删除"呢用个数字 4,"可创建"呢用一个数字 5。哎,这就是五种基本权限。注意啊,是基本权限。
js
const READ = 1;
const WRITE = 2;
const SHARE = 3;
const DELETE = 4;
const CREATE = 5;
const READ_WRITE_SHARE = 6;
而一个用户真正要去记录它的权限的时候,它有可能会涉及到权限之间的组合。那么在组合的时候,比如说,一个用户对这篇文章,它既有可读,又有可写,又可以分享,就要把三个组合起来。那组合起来的话,你是不是得去定义一个新的数字?因为原本是没有这个数字的。原本是用数字,用数字的话,你不就保存的东西独立了吗?我们希望就保存一个数字就行了。那你要组合的话,可能你还得去写个权限,叫"可读可写可分享",然后呢是数字 6。你仔细想一下,这个事情就变得恐怖了。五种基本权限,那组合起来的话,两两组合、三三组合、四四组合、五五组合,那组合起来那就是非常多了,而且将来很不好维护。如果说将来再多一种基本权限的话,那就完蛋了。
所以我们必须要找到一种更加合适的方式,来记录这个权限数字。那怎么来做呢?看好了,咱们就可以使用二进制的。比如说,我们可以把这个"可读"写成一个二进制,你避开了一个五位二进制,它最后一位为 1,只要最后一位为 1,就表示它具有可读权限。然后"可写"呢,那就是这一位为 1。"可分享"呢,这一位。"可删除"呢,这一位。"可创建"呢,这一位。当然你也可以用十进制去写,比如这里的十进制就是 1,这里的十进制就是 2,这里的十进制就是 4,然后八,然后十六。哎,十六可能看得没有那么清楚。因为二进制写好了,看得非常清楚,然后位置是控制什么样的权限的,看得非常清楚。
js
// 1. 定义基本权限(使用二进制位表示)
const PERMISSIONS = {
READ: 0b00001, // 二进制,十进制是 1
WRITE: 0b00010, // 二进制,十进制是 2
SHARE: 0b00100, // 二进制,十进制是 4
DELETE: 0b01000, // 二进制,十进制是 8
CREATE: 0b10000, // 二进制,十进制是 16
};
// 简单来说,每个权限都是一个2的幂次方的数字。
为什么这样做就能解决问题呢?你想了,如果说将来我们要做一个"可读、可写、可分享"的权限出来,是不是我只需要去写,让每个二进制位为 1 就行了?明明要要要,这个位置表示可读,这个位置表示可写,这个位置表示可分享。怎么写呢?而且写的时候都不用写死,我只用这些变量,就可以把它组合起来。"可读"或"可写"的,把它用"或"运算,这是个二进制运算,对吧?只要用位运算,那就是 |
,把它保存到用户的权限里面。这样子,用户的权限里面仍然只是一个数字,但是这个数字它就记住了它里边具有哪些权限了,就是合并在一个变量里边,对不对?
js
// 2. 组合权限 - 使用按位或 (|) 操作符
// 口诀:有1则1
let zhangsanPermission = PERMISSIONS.READ | PERMISSIONS.WRITE;
console.log(zhangsanPermission); // 输出 3 (因为 0b00001 | 0b00010 = 0b00011)
// 张三的权限数字是 3。这个3里面,就同时包含了"读"和"写"。
而且用这种方式来组合,它还可以做各种灵活的运算。比如,一个权限都是从远程获取的,从远程获取的,那么获取到过后,它肯定是一个数字。拿到这个数字过后,你也不知道是多少。那么我如何通过这个数字来判断它里边有没有"可读"权限,对吧?是否具有可读权限,怎么来判断呢?这个什么呢?我们也可以利用二进制运算来得到这个结果。
所以这里看 React 源码里边,它为什么要这样写?就是因为它可以方便对这些字段进行组合,以及方便在后边进行运算。你看它这里,是不是可以通过这些运算来合并?所以我们现在进行组合了。
js
const perm = READ | WRITE | SHARE;
function include(permission, permBit) {
return (permission & permBit) === permBit
}
// 是否包含可读权限
console.log(include(perm, READ)) // true
所以我们学源码的意义在哪?学源码的意义绝对不是说,我为了去把这个 React 的源码讲清楚,那是次要的。因为你要理解 React 的话,其实我讲原理就够了,不用讲到源码。学习源码最终的目的,一定是为你的开发赋能。就是通过去理解一些高手的写法,从而对你将来遇到一些复杂问题的时候,能够帮你打开一些思路。
检查权限
那我如何来判断这个玩意里边是不是包含"可读"?我们可以做这么一个运算。你看我们现在有一个用户的权限,五位,我都不知道他是啥,我怎么来判断他这一位是不是 1 呢?我们可以这样:我就用这个用户的权限,与这个"可读"权限来求一个"与"运算。你看一下"与"运算是什么样的特点?这两位都必须要为 1 才为 1,对吧?否则的话,那就为 0 了。那下边那个"与"运算了,前四位都是 0,所以说不管上面是啥,那这里是不是一定是 0?然后最后一位呢,他就取决于"与"运算里边,下边是固定为 1 的,上面为 1,那这里就为 1;上面为 0,那这里就为 0。所以说我们就可以判断了,对吧?你把你的权限,跟一个"可读"权限来求一个"与"运算,如果结果仍然等于"可读"权限的话,那么就说明你这一位为 1,你就具有可读权限。
js
????1
11110
____
????0
然后你可以封装一个方法,说 hasPermission
这个方法,来看是否包含某一种权限。你可以传一个权限,然后给我一个我要判断的权限位。好,肯定形参是吧?然后我就给他来求一个与运算,看求了过后是不是等于他,完事了,对不对?来吧,我们来试一下 hasPermission
,我这个权限里边是不是包含可读权限?然后判断一下,运行一下,输出 true
。对了,非常简单。
那再比方说,我们有可能还会希望能够去掉某一个权限。比如说,我这个权限拿到一个数字,但我说,我要从这个数字里面把"可读"权限去除掉。那要怎么做呢?
那还是通过二进制运算呗。
就是我要通过一种运算,把这一位置 0,不管他原来是啥,我要给他置 0,前边的不动,老样子。是不是我们还可以利用"与"运算?前边的这四位全部为 1,那你想前边四位全部为 1,是不是不管上面是啥,得到的结果一定是上面的东西不变?因为我对求"与"运算嘛。那这一位呢,我要把它写 0,不管上面是啥都会被置 0,对不对?那就把"可读"权限给删除掉了。那这个值怎么来呢?这个值不就是"可读"权限取反吗?对这个权限移除吧,先移除掉里边的可读,然后再看一下它里边是否包含可读?那现在就不包含了。说并且复制的,对吧?
js
function remove(permission, permBit) {
return permission & ~permBit;
}
console.log(include(remove(perm, READ), READ));
js
// 4. 移除权限 - 使用按位与非 (& ~) 操作
// 第一步:对要移除的权限取反 (~),比如 ~WRITE 会变成 0b...11111101
// 第二步:再和用户权限进行 与(&) 操作,原来WRITE的位置强制变0,其他位不变。
function removePermission(userPermission, removePermission) {
return userPermission & ~removePermission;
}
console.log('移除前张三的权限:', zhangsanPermission); // 3 (0b00011)
zhangsanPermission = removePermission(zhangsanPermission, PERMISSIONS.WRITE);
console.log('移除"写"权限后:', zhangsanPermission); // 1 (0b00001)
// 再检查一下他是否还能写
console.log('移除后张三可写吗?', hasPermission(zhangsanPermission, PERMISSIONS.WRITE)); // false
通过一些二进制运算,就可以非常方便的来封装这些处理方法了,而且整个过程的效率是极高的。