概览

本文详细剖析JavaScript的位运算符,其涉及的计算机原理和操作效果。
然后从实战的角度出发,罗列相关的应用场景。

位操作符概览

运算符描述示例
按位与(AND)两个操作数对应的比特位都是1时,结果才为1,否则为01011 & 0111 = 0011
按位或(OR)两个操作数对应的比特位至少有一个1时,结果为1,否则为01011 | 0111 = 1111
按位异或(XOR)两个操作数对应的比特位有且只有一个1时,结果为1,否则为01011 ^ 0111 = 1100
按位非(NOT)逐个反转操作数的比特位,即0变成1,1变成0~1011 = 0100
左移通过从右推入零向左位移,并使最左边的位脱落。1011 << 1 = 10110
有符号右移通过从左推入最左位的拷贝来向右位移,并使最右边的位脱落。01011 >> 1 = 00101
无符号右移通过从左推入零来向右位移,并使最右边的位脱落。01011 >>> 1 = 00101

位操作支持多少位?

js只支持32位二进制数的位操作,也即能处理的最大十进制数字是 4294967295

1
parseInt('11111111111111111111111111111111', 2);  // 4294967295

验证下超过32位二进制数的位操作:

1
2
3
4
5
6
// 33位二进制数,得到十进制数字 8589934591
parseInt('111111111111111111111111111111111', 2); // 8589934591

// 对数字进行无符号位右移
8589934591 >>> 0; // 4294967295
4294967295 >>> 0; // 4294967295

可以看出,数字 85899345914294967295 进行 无符号位右移0位 操作,得到的结果是一样的。
产生这样结果的原因,是js的位操作实现,只支持32位

注意:ECMAScript 中的所有数值都以IEEE754 64位格式存储,只是在位操作的时候 ,需要转换成32位进行操作。

负数的无符号右移

-2 >>> 1为什么输出2147483647?

-2在运算中,是用补码表示,即1 1111111111111111111111111111110
其中,第1位是符号位。 符号位1代表当前数字是负数。

-2无符号右移1位,则最右边的0脱落,剩下31位1111111111111111111111111111111,接着,在左侧补0,得到01111111111111111111111111111111

01111111111111111111111111111111代表十进制数2147483647

状态控制

场景:
以下使用React+TypeScript,实现游戏状态机的状态流转,根据状态渲染对应的操作按钮。

1
2
3
4
5
6
7
// 游戏状态定义
export enum GAME_STATE{
INIT = 1 << 0, // 二进制表示:00001
JOIN = 1 << 1, // 二进制表示:00010
PREPARE = 1 << 2, // 二进制表示:00100
PLAY = 1 << 3, // 二进制表示:01000
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 根据状态渲染按钮
function RenderButton({state, changeState}){
if((state & GAME_STATE.PLAY) === GAME_STATE.PLAY){
return null;
}

if((state & GAME_STATE.INIT) === GAME_STATE.INIT){
return <button
onClick={() => changeState(GAME_STATE.JOIN)}
>加入游戏</button>
}

if((state & GAME_STATE.JOIN) === GAME_STATE.JOIN){
return <button
onClick={() => changeState(GAME_STATE.PREPARE)}
>准备游戏</button>
}

if((state & GAME_STATE.PREPARE) === GAME_STATE.PREPARE){
return <button
onClick={() => changeState(GAME_STATE.PLAY)}
>开始游戏</button>
}

return null;
}

// 渲染游戏页面
function Page(){
const state = useRef(GAME_STATE.INIT);

const changeState = useCallback((newState) => {
state.current = newState;
}, [state]);

return (<div>
// ....other code
<RenderButton state={state.current} changeState={changeState} />
</div>);
}

权限控制

设计一个权限控制,用于不同角色对网站文章的权限分配。

1
2
3
4
5
6
7
8
9
10
11
12
export enum ARTICLE_RULE{
VIEW = 1 << 0, // 查看文章
EDIT = 1 << 1, // 编辑文章
PUBLISH = 1 << 2, // 发布文章
DELETE = 1 << 3, // 删除文章
}

export enum ROLE{
GUEST = ARTICLE_RULE.VIEW, // 访客
ADMIN = ARTICLE_RULE.VIEW | ARTICLE_RULE.EDIT | ARTICLE_RULE.PUBLISH | ARTICLE_RULE.DELETE, // 超级管理员
OPERATOR = ARTICLE_RULE.VIEW | ARTICLE_RULE.EDIT | ARTICLE_RULE.PUBLISH// 运营
}
1
2
3
4
5
6
7
8
if(user.role === 'admin'){ 
console.log("user拥有admin权限");
user.rule = ROLE.ADMIN; // 赋予角色权限
}

if((user.rule & ARTICLE_RULE.DELETE) === ARTICLE_RULE.DELETE){
console.log("user拥有删除文章权限");
}

判断奇偶数

奇数,最末尾1位,一定是1
所以将数字与1(二进制表示为:00000000000000000000000000000001)作位操作&
如果等于1,则是奇数。

1
2
3
function isOdd(number){
return number & 1 === 1;
}

交换两个变量的值

1
2
3
4
5
6
7

let a = 1;
let b = 2;

a = a ^ b; // 这一步,a缓存了 a ^ b 的结果
b = a ^ b; // 等价为: b = a ^ b ^ b, 其中 b ^ b = 0; 所以 b = a ^ 0 = a
a = a ^ b; // 同上

判断整数是否相等

1
2
3
function isEqual(number1, number2){
return (number1 ^ number2) === 0;
}

判断是否为负数

如果是负数,则对数字进行 无符号右移 位操作,会变成一个新的数字。
所以,如果是负数,则两个数字不相等。

1
2
3
function isMinus(number){
return number !== (number >>> 0);
}

正浮点数取整

1
2
3
function toInt(floatNumber){
return floatNumber >>> 0;
}

正负浮点数取整

1
2
3
function toInt(floatNumber){
return floatNumber | 0;
}
1
2
3
function toInt(floatNumber){
return ~~floatNumber;
}
1
2
3
function toInt(floatNumber){
return floatNumber >> 0;
}

十进制转换成二进制

1
2
3
function dec2bin(dec){
return (dec).toString(2);
}

二进制转换成十进制

1
2
3
function bin2dec(bin){
return parseInt(`${bin}`, 2)
}

参考