← 中文资源

简体中文版本

位标志权限集合工具

用单个 32 位掩码表示最多 30 个权限,并提供构建、判断、增删、切换与枚举能力。

阅读英文原文

功能说明

该示例把一组命名权限映射成位标志,并用一个 32 位整数保存当前组合。相比字符串数组,位掩码占用小、比较快、易于持久化,适合会话权限、功能开关或角色能力集合。它提供 mask、has、add、remove、toggle 与 toNames,覆盖常见的读写操作。

关键代码

createFlagSet 在初始化时为每个名称分配 1 << index,并检查名称非空且不重复。resolve 在遇到未知名称时抛错,避免因为拼写错误静默得到 0。mask 用按位或合并多个权限,has 用按位与非零判断,remove 用按位与取反清除,toggle 用异或翻转。toNames 按注册顺序过滤出已启用的名称。

使用注意

最多支持 30 个标志,因为 JavaScript 的按位运算基于有符号 32 位整数,第 31 位会变成负数,容易在序列化时出问题。位掩码体积小但可读性差,持久化时应同时保存版本号,新增权限只追加不复用旧位。权限判断只反映掩码内容,不替代服务端授权校验。不要把用户可控的位掩码直接当作可信权限。

验证方式

创建 read、write、admin 三个标志,合并 read 与 write 后断言掩码为 3 且名称列表正确。移除 write、切换 admin 后断言 has 返回 false 且列表变为 read 与 admin。测试重复名称、空名称、超过 30 个标志以及未知标志分别抛错。

代码示例

export function createFlagSet(names) {
  if (!Array.isArray(names) || names.length === 0) {
    throw new TypeError("names must be a non-empty array");
  }
  if (names.length > 30) throw new RangeError("at most 30 flags are supported");
  const bits = new Map();
  const all = [];
  for (const name of names) {
    if (typeof name !== "string" || name.trim() === "") {
      throw new TypeError("flag names must be non-empty strings");
    }
    const normalized = name.trim();
    if (bits.has(normalized)) throw new Error(`duplicate flag "${normalized}"`);
    bits.set(normalized, 1 << bits.size);
    all.push(normalized);
  }
  const resolve = (name) => {
    const bit = bits.get(name);
    if (bit === undefined) throw new Error(`unknown flag "${name}"`);
    return bit;
  };
  return {
    names: () => [...all],
    mask(...selected) {
      return selected.reduce((mask, name) => mask | resolve(name), 0);
    },
    has(mask, name) {
      return (mask & resolve(name)) !== 0;
    },
    add(mask, name) {
      return mask | resolve(name);
    },
    remove(mask, name) {
      return mask & ~resolve(name);
    },
    toggle(mask, name) {
      return mask ^ resolve(name);
    },
    toNames(mask) {
      return all.filter((name) => (mask & resolve(name)) !== 0);
    },
  };
}

const permissions = createFlagSet(["read", "write", "admin"]);
let mask = permissions.mask("read", "write");
console.log(mask, permissions.toNames(mask));
mask = permissions.remove(mask, "write");
mask = permissions.toggle(mask, "admin");
console.log(permissions.has(mask, "write"), permissions.toNames(mask));