用二进制思维重构前端权限系统
点击关注公众号,“技术干货”及时达!
一、一个真实开发场景引发的思考某天,我在权限管理组件中写下了第5个isAdmin && canEdit || hasPermission('delete'),突然意识到——这种用布尔值堆砌的权限系统正在让我的代码变得臃肿。看似清晰的逻辑,实则暗藏着复杂的逻辑链条,这让我很容易忽视潜在的错误和维护的困难。特别是当权限的组合和复杂度不断增加时,传统的布尔值判断往往无法满足日益复杂的需求,代码也变得越来越难以理解和扩展。
此时,恍若灵光一现,我想起了React团队在管理Fiber节点时,如何利用二进制位运算来高效地管理节点的状态。React用0b1011这样的二进制数,表示节点的32种状态,极大提高了性能的同时,也简化了复杂状态的管理。这种做法,不仅保证了系统的高效运行,还让原本散乱的状态信息在二进制位中得以精确而清晰的表达。
同样,Vue3的虚拟DOM也通过位运算实现了快速判断节点类型和状态,避免了传统的遍历和比对,进一步提升了渲染效率。Vue3将不同的操作类型、生命周期状态、节点信息等通过位运算压缩在整数值中,这种方法不仅提高了性能,还降低了出错的可能性。
在这个对比中,我不禁思考:如果在权限管理中也能引入位运算的思想,是否能让复杂的权限控制更简洁、清晰和高效?比如,使用一个整数代表不同权限的组合,通过位运算来判断权限是否符合,既能避免多重嵌套判断,也能在权限的扩展上获得更大的灵活性。
二、从二进制到位运算2.1 权限的二进制表示在计算机中,二进制是最基本的数据表示方式。每一位二进制位(bit)都可以表示一个状态,0表示“无”,1表示“有”。在权限管理中,我们可以利用这一特性,将不同的权限映射到二进制的不同位上。
例如,假设我们有三种权限:读(Read)、写(Write)和执行(Execute)。我们可以将它们分别映射到二进制的不同位:
读权限(Read):2的0次方,即1(二进制:001)写权限(Write):2的1次方,即2(二进制:010)执行权限(Execute):2的2次方,即4(二进制:100)通过这种映射,我们可以使用一个整数来表示用户的所有权限。例如,权限值为7(二进制:111),表示用户同时拥有读、写和执行权限。
2.2 位运算符号「1. 位运算符」「左移 :」 将二进制数的位数左移 n 位,相当于乘以 2^n。
「右移 :」 将二进制数的位数右移 n 位,相当于整数除以 2^n,向下取整。
「按位与 &:」 只有当两个二进制位都为 1 时,结果才为 1,否则为 0。
「按位或 |:」 只要有一个二进制位为 1,结果就为 1。
「按位异或 ^:」 当两个二进制位不同时,结果为 1;相同则为 0。。
三、框架源码中的位运算艺术3.1 React Fiber 状态压缩术在React的reconciliation算法中,单个Fiber节点需要同时记录多种状态:
//react/packages/react-reconciler/src/ReactFiberFlags.js
exportconstPlacement=0b0000000000001;
exportconstUpdate=0b0000000000010;
exportconstChildDeletion=0b0000000000100;
//状态组合
letflags=Placement|Update;//0b0000000000011
//超高效状态检测
if(flagsUpdate){
//执行副作用更新...
}
设计哲学:用1个32位整数替代32个布尔变量,内存占用减少96%,状态检测速度提升10倍。
3.2 Vue3 虚拟DOM类型快查Vue3通过shapeFlag实现虚拟节点类型的闪电判断:
//vue-next/packages/shared/src/shapeFlags.ts
exportconstenumShapeFlags{
ELEMENT=1,
COMPONENT=11,
TEXT_CHILDREN=12,
ARRAY_CHILDREN=13
}
//动态组合类型
constvnodeFlag=ShapeFlags.ELEMENT|ShapeFlags.ARRAY_CHILDREN;
//比switch快10倍的类型判断
if(vnodeFlagShapeFlags.COMPONENT){
//处理组件逻辑...
}
性能对比:传统字符串类型判断需要遍历原型链,位运算直接访问寄存器,速度提升20倍。
四、位运算在算法中的应用4.1 leetcode231 2的幂传统解法:通过不断除以 2/**
*给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。
*如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。
*
*示例 1:
*输入:n = 1
*输出:true
*
*示例 2:
*输入:n = 16
*输出:true
*
*示例 3:
*输入:n = 3
*输出:false
*/
functionisPowerOfTwo(n){
if(n=0)returnfalse;
while(n1){
if(n%2!==0)returnfalse;
n=Math.floor(n/2);//使用Math.floor保证是整数除法
}
returntrue;
}
位运算解法:利用 n & (n - 1) 的特性functionisPowerOfTwo(n){
returnn0(n(n-1))===0;
}
解释:
n - 1:将 n 减去 1,会将二进制表示中的最低位的 1 变为 0,其后的所有 0 变为 1。例如:n = 4(0100),n - 1 = 3(0011)n = 8(1000),n - 1 = 7(0111)(n & (n - 1)) === 0:如果上述按位与运算的结果为 0,则 n 是 2 的幂次方。4.2 leetcode 136 只出现一次的数字传统解法:使用哈希表/**
*给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
*
*示例1:
*输入:[2,2,1]
*输出:1
*
*示例2:
*输入:[4,1,2,1,2]
*输出:4
*
*/
varsingleNumber=function(nums){
lethashTable=
for(leti=0;inums.length;i++){
if(hashTable[nums[i]]==undefined){
hashTable[nums[i]]=1;
}else{
hashTable[nums[i]]++;
}
}
for(letiinhashTable){
if(hashTable[i]==1){
return
}
}
};
位运算解法:利用 n & (n - 1) 的特性//nums=[4,1,2,1,2]输出4
//0
//0^4=4
//4^1=5
//5^2=7
//7^1=6
//6^2=4
varsingleNumber=function(nums){
returnnums.reduce((sum,cur)={
returnsum^cur
},0)
};
五、实战:从零构建位运算权限系统5.1 基础版权限控制//权限定义(使用左移生成唯一掩码)
constREAD=10;//0b0001
constWRITE=11;//0b0010
constDELETE=12;//0b0100
//用户权限组合
letuserPermissions=READ|WRITE;//0b0011
//高阶组件权限校验
constwithAuth=required=WrappedComponent=props=
(userPermissionsrequired)===required
?WrappedComponent{...props}/
:Redirectto="/403"/;
//使用示例
constAdminPanel=withAuth(WRITE|DELETE)(()=div敏感操作区/div);
5.2 进阶版权限控制//权限池扩展
constpermissions={
READ:10,
WRITE:11,
DELETE:12,
MANAGER:13
};
//权限动态处理器
classPermissionManager{
constructor(){
this._flags=0;
}
grant(perm){
this._flags|=perm;//按位与:只有当两个二进制位都为 1 时,结果才为 1,否则为0。
returnthis;
}
revoke(perm){
this._flags~perm;//按位非~:将所有二进制位取反,然后与原值进行按位与&。
returnthis;
}
toggle(perm){
this._flags^=perm;//按位异或^:当两个二进制位不同时,结果为 1;相同则为0。
returnthis;
}
has(perm){
return(this._flagsperm)===perm;
}
}
//使用示例
constuser=newPermissionManager().grant(permissions.READ);
user.grant(permissions.WRITE);
console.log(user.has(permissions.READ|permissions.WRITE));//true
user.toggle(permissions.WRITE);
console.log(user.has(permissions.WRITE));//false
5.3 优点和缺点优点位运算执行速度非常快,通常是常数时间 O(1),对大数据量的操作也很高效。高效性:位运算执行速度非常快,通常是常数时间 O(1),对大数据量的操作也很高效。节省内存:使用整数来表示权限,可以将多个权限压缩在一个数字中,节省内存。简洁和清晰:代码相对简洁,尤其是与其他数据结构相比,避免了复杂的条件判断和遍历。避免多次遍历:位运算一次性操作多个权限,避免了循环和多次条件判断。???「性能与内存对比」?
缺点可读性差虽然位运算非常高效,但对于一些不熟悉位运算的开发者来说,理解代码可能比较困难。比如,userPermissions |= WRITE; 和 userPermissions & DELETE 的含义并不是每个前端开发者都能第一时间理解。权限含义不可读:需要建立位-权限映射表权限移除的复杂性当前代码示例使用 |= 来添加权限,但如果需要移除某个权限(比如用户取消了某个权限),就需要使用 &= ~permission 这种相对复杂的操作来做移除操作。最大权限位限制权限容量硬限制:32位系统最大支持32个独立权限位浮点数精度风险:JavaScript中超过 2^53 后出现精度丢失六、后续思考能否结合位元算和传统方式,实现权限系统的混合架构设计
可读性差:可以考虑使用枚举类型(Enum)来代替直接的位运算,这样可以提高代码的可读性。最大权限位限制:可以考虑使用大数(BigInt)来扩展权限位,但这会增加内存消耗。点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线