使用位运算来做用户鉴权其实并不是一件新鲜事,已经有不少人讲过了。不过最近在看vue3源码的时候发现vue3在对VisualDOM做patch操作的时候竟然也使用了位运算进行flag的判断,便忽然来了兴趣,想要好好说道说道。
首先看看来看看vue3源码,已经去除了不必要的注释
patchFlags是VisualDOM中对vnode的类型标记,在更新DOM树的时候会根据vnode的类型来使用不同的更新策略,这里不展开说了,我们主要看这里对类型的定义。
(patchFlags.ts Github源码地址)
// Patch flags can be combined using the | bitwise operator and can be checked
// using the & operator, e.g.
//
// const flag = TEXT | CLASS
// if (flag & TEXT) { ... }
//
// Check the `patchElement` function in './renderer.ts' to see how the
// flags are handled during diff.
export const enum PatchFlags {
TEXT = 1,
CLASS = 1 << 1,
STYLE = 1 << 2,
PROPS = 1 << 3,
FULL_PROPS = 1 << 4,
HYDRATE_EVENTS = 1 << 5,
STABLE_FRAGMENT = 1 << 6,
KEYED_FRAGMENT = 1 << 7,
UNKEYED_FRAGMENT = 1 << 8,
NEED_PATCH = 1 << 9,
DYNAMIC_SLOTS = 1 << 10,
// SPECIAL FLAGS -------------------------------------------------------------
// Special flags are negative integers. They are never matched against using
// bitwise operators (bitwise matching should only happen in branches where
// patchFlag > 0), and are mutually exclusive. When checking for a special
// flag, simply check patchFlag === FLAG.
HOISTED = -1,
BAIL = -2
}
// dev only flag -> name mapping
export const PatchFlagNames = {
[PatchFlags.TEXT]: `TEXT`,
[PatchFlags.CLASS]: `CLASS`,
[PatchFlags.STYLE]: `STYLE`,
[PatchFlags.PROPS]: `PROPS`,
[PatchFlags.FULL_PROPS]: `FULL_PROPS`,
[PatchFlags.HYDRATE_EVENTS]: `HYDRATE_EVENTS`,
[PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
[PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
[PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
[PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,
[PatchFlags.NEED_PATCH]: `NEED_PATCH`,
[PatchFlags.HOISTED]: `HOISTED`,
[PatchFlags.BAIL]: `BAIL`
}
可以看到,除了最后两种特殊类型外共有11种类型,每一种的值都是依次将1左移一位得到的。
在文档开头的注释里作者甚至贴心的给出了用法:使用“|”来进行组合赋值,使用“&”来进行检查。
下面我们来写一个简单的例子
以常用的Linux命令chmod 777为例,7代表可读可写可执行,写成二进制就是0111。具体如下:
其中,每一种权限的数值就是将1左移1到3位得到的。
不难理解,如果是可读可执行则对应0101,值为5;可写可执行对应0011,值为3,就此我们来写一个简单的鉴权系统。
const Permissions = {
X: 1,
W: 1 << 1, // 0010 -> 2
R: 1 << 2, // 0100 -> 4
};
let userPermission = 0; // 初始为0,即无权限
/** 赋权 */
userPermission |= Permissions.X; // 赋予可执行权限,此时 userPermission 为 0001
userPermission |= Permissions.W; // 赋予写权限,此时 userPermission 为 0011
/** 鉴权 */
if ( userPermission & Permissions.X ) { // 0011 & 0001 结果为1,返回真
console.log('此用户有可执行权限');
}
if ( userPermission & Permissions.W ) { // 0011 & 0010 结果为2,返回真
console.log('此用户有写权限');
}
if ( userPermission & Permissions.R ) { // 0011 & 0100 结果为0,返回假
console.log('此用户有读权限');
}
可以直接按F12将代码粘贴过去运行,可以看到浏览器最后输出了“此用户有可执行权限“和”此用户有写权限”。
具体是怎么实现的呢?
我们的Permissions共有3中权限,而用户权限userPermission默认为0,代表无权限。
在赋权操作中,将userPermission 与Permissions进行或运算,此时userPermission对应位上的0会变成1,即代表用户拥有了此权限。
而在鉴权操作中,将userPermission 与Permissions进行与运算,此时若userPermission对应位上的数字为1,则会返回一个大于0的值,也就是真值,代表用户拥有此权限;而用户没有此权限时返回的结果就是0了,鉴权为假。
如此,一个简单的用户鉴权系统就完成了。
在renderer.ts中作者也向我们展示了具体的使用方式,感兴趣的不妨点击链接去看看。