前言: & | ^ ~ << >> 这些是啥? 有啥用? 跟前端有啥关系?
位运算在前端中的应用(权限管理)
const TEXT = 1; // 0b001
const ELEMENT = 1 << 1; // 0b010
const STYLE = 1 << 2; // 0b100
/*授权*/
/**0b100 | 0b010 = 0b110 */
let vnodeType = STYLE | ELEMENT;
/*鉴权*/
/**0b110 & 0b001 = 0b000 false */
vnodeType & TEXT // false
/**0b110 & 0b010 = 0b010 2 true */
vnodeType & ELEMENT // true
/**0b110 & 0b100 = 0b100 4 true */
vnodeType & STYLE // true
/*删除权限*/
/**0b110 ^ 0b010 = 0b100 */
vnodeType ^= ELEMENT
如果你没有掌握位运算的相关知识,可能会想到用数组来模拟, 或者用对象来存储;
那么权限操作的复杂度就到了O(n)之上, 而位运算则是O(1)的复杂度。
位运算概览
| 标题 | 描述 | 规则 |
|---|---|---|
| & | 与 | 都为1时,为1 反之为0 |
| | | 或 | 都为0时,为0 反之为1 |
| 异或 | 相同时为0, 反之为1 |
(0b010 & 0b111).toString(2) // 0b010
(0b010 | 0b111).toString(2) // 0b111
(0b010 ^ 0b111).toString(2) // 0b101
牛刀小试,加深印象
LeetCode 231. 2 的幂
因为2^n一定是高位为1其他位都是0,例如:
0b10, 0b100, 0b1000
那么, 2^n - 1 则是:
0b01, 0b011, 0b0111
与操作符的运算规则是都为1时为1,否则为0,
所以只要2^n和2^n-1相&之后为0,则一定是2的正整数幂次方
/*给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。
如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。
*/
exports.isPowerOfTwo = (n) => {
while (n > 1) {
n = n / 2;
}
return n === 1;
};
exports.isPowerOfTwo2 = (n) => {
return n >= 1 && (n & (n - 1)) === 0;
};
LeetCode 136. Single Number
异或(^)运算规则是
相同时为0,否则为1
0b10 ^ 010 = 0b00
结论: 任何一个数跟自身 异或(^) 都为0
/*给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。*/
exports.singleNumber = (nums) => {
let ret = 0;
nums.forEach((n) => {
ret ^= n;
});
return ret;
};
小结:
- 异或运算(^), 任何数跟自身^都是0; 喜好0
- 与(&): 喜好1
- 或(|): 喜好0
- 对位运算逐渐清晰, 不再畏惧位运算
- 位运算原来还可以这么用
意外发现的疑惑点?
1<<2 === 2**2 // true
1<<30 === 2**30 // true
1<<31 === 2**31 // false
1<<31+1 ===1 // true
不是说JavaScript的最大安全数是$2^{53}$-1吗?为啥1<<31就溢出了?
参考
- 位运算(&、|、^、~、>>、<<)
- 花果山大圣(前端啃算法)