使用位运算来做用户鉴权其实并不是一件新鲜事,已经有不少人讲过了。不过最近在看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中作者也向我们展示了具体的使用方式,感兴趣的不妨点击链接去看看。

11-27 00:56