功能说明
该示例把一组命名权限映射成位标志,并用一个 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));