位运算
进制转换
十进制转二进制
分为整数部分和小数部分
整数部分
方法:除2取余,逆序排列,即每次将整数部分除以 2,余数为该位权上的数,而商继续除以 2,余数又为上一个位权上的数,这个步骤一直持续下去,直到商为 0 为止,最后读数时候,从最后一个余数读起,一直到最前面的一个余数。
12 -> 1100
12 除 2, ----- 余 0
6 除 2, ----- 余 0
3 除 2, ----- 余 1
1 除 2, ----- 余 1
小数部分
方法:乘2取整,顺序排列,即将小数部分乘以 2,然后取整数部分,剩下的小数部分继续乘以 2,然后取整数部分,剩下的小数部分又乘以 2,一直取到小数部分
0.125 -> 0.001
0.125 乘 2 = 0.25 ----- 0
0.25 乘 2 = 0.5 ----- 0
0.5 乘 2 = 1 ----- 1
12.125 -> 1100.001
二进制转十进制
方法:按权相加法,即将二进制每位上的数乘以权,然后相加之和即是十进制数。
10101000.001 -> 168.125
1x27 + 0x26 + 1x25 + 0x24 + 1x23 + 0x22 + 0x21 + 0x20 + 0x2-1 + 0x2-2 + 1x2-3
0.1 + 0.2 !== 0.3
0.1 -> 0.0001100110011001100110011001100110011001100110011001101
0.2 -> 0.001100110011001100110011001100110011001100110011001101
位运算
按位与运算(&)
两个位都为 1 时,结果才为 1,否则为 0
1&1 为 1,0&0 为 0,1&0 也为 0
0101 & 0011 => 0001 (即 5 & 3 = 1)
按位或运算(|)
有一个为 1 时,结果就为 1,两个都为 0 时结果才为 0
1|1 为 1,0|0 为 0,1|0 为 1
0101 | 0011 => 0111 (即 5 | 3 = 7)
按位异或运算(^)
不同时,结果为 1,相同时结果为 0
0^1 为 1,0^0 为 0,1^1 为 0‘
0101 ^ 0011 => 0110 (即 5 ^ 3 = 6)
取反运算(~)
~1 为 0,~0 为 1
~x 等同于 -(x + 1)。例如 ~-1 = 0
左移运算(<<)
全部左移若干位,高位丢弃,低位补 0
常用场景: 乘以 2 的 n 次方
右移运算(>>)
全部右移若干位,低位丢弃,高位补 0 或 1。如果数据的最高位是 0,那么就补 0;如果最高位是 1,那么就补 1。
常用场景: 除以 2 的 n 次方
无符号右行移(>>>)
忽略符号位,空位都以 0 补齐
位运算在前端的应用
1. 权限与状态管理
在很多系统和现代前端框架(如 Vue 3 的 ShapeFlags,React 的 Lane 模型)中,经常用二进制位来表示不同的状态或权限。这种方式极其节省内存,且运算速度极快。
// 定义权限 (1, 2, 4, 8)
const READ = 1 << 0; // 0001 (1)
const WRITE = 1 << 1; // 0010 (2)
const EXECUTE = 1 << 2; // 0100 (4)
const DELETE = 1 << 3; // 1000 (8)
// 1. 赋予权限 (按位或 |)
let userRole = READ | WRITE; // 0011 (拥有读和写权限)
// 2. 校验权限 (按位与 &)
// 只要结果还是原来那个权限的值,就说明拥有该权限
const hasWrite = (userRole & WRITE) === WRITE; // true
const hasDelete = (userRole & DELETE) === DELETE; // false
// 3. 删除权限 (按位与 & + 按位非 ~)
userRole = userRole & ~WRITE; // 0001 (去掉了写权限,只剩读)
// 4. 切换权限 (按位异或 ^)
userRole = userRole ^ EXECUTE; // 之前没有执行权限,现在加上了
2. 快速取整(代替 Math.floor / Math.trunc)
位运算会将操作数转换为 32 位整数,利用这个副作用,可以直接去掉浮点数的小数部分。比调用 Math 对象的方法更快。
const num = 3.14159;
// 以下三种写法都可以快速向下取整(或截断小数)
const a = num | 0; // 3
const b = ~~num; // 3 (双重按位非,前端源码里非常常见)
const c = num >> 0; // 3
// 注意:对负数的处理等同于 Math.trunc,而非 Math.floor
~~(-3.14); // -3
Math.floor(-3.14); // -4
(注意:仅适用于 32 位带符号整数范围,即 $-2147483648$ 到 $2147483647$。超过这个范围会失真!)
3. 奇偶数判断
使用 & 1 取出二进制的最低位。最低位是 1 则是奇数,是 0 则是偶数。性能略好于 % 2。
const isOdd = (num) => (num & 1) === 1;
const isEven = (num) => (num & 1) === 0;
console.log(isOdd(5)); // true
4. 判断索引是否存在(经典的 ~ 操作)
在 ES6 的 Array.prototype.includes 出现之前,前端判断字符串或数组包含某个元素常借助 indexOf,而 indexOf 找不到时返回 -1。
因为 ~(-1) 的结果是 0(在 JS 中作为布尔值是 falsy),所以可以这样写:
const str = "hello world";
// 老写法
if (str.indexOf("world") !== -1) { ... }
// 装逼/极客写法
if (~str.indexOf("world")) {
// 如果包含 "world",indexOf 就会返回非 -1 的数,按位非之后就不是 0,进入 if
}
5. RGB 与 Hex 颜色的互转 (Canvas/图形处理)
在 Canvas 像素级操作或者 WebGL 中,颜色计算非常依赖位运算。
RGB 转 16 进制颜色:
function rgbToHex(r, g, b) {
// 1 << 24 是为了保证结果是 6 位数 (避免 0 的情况被截断)
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
rgbToHex(255, 128, 0); // "#ff8000"
16 进制转 RGB:
function hexToRgb(hex) {
// 去掉 '#' 并转为 10 进制整数
const color = parseInt(hex.replace('#', ''), 16);
const r = (color >> 16) & 255; // 右移 16 位取高八位
const g = (color >> 8) & 255; // 右移 8 位取中八位
const b = color & 255; // 直接取低八位
return `rgb(${r}, ${g}, ${b})`;
}
6. 两个变量交换值(不借助第三个变量)
使用异或运算的特性:a ^ a = 0,a ^ 0 = a。
let a = 5; // 0101
let b = 3; // 0011
a = a ^ b;
b = a ^ b;
a = a ^ b;
// 此时 a = 3, b = 5
(虽然解构赋值 [a, b] =[b, a] 在现代 JS 中更可读,但异或交换是经典的计算机底层操作。)
总结与面试建议
- 优点:执行速度极快,在处理组合状态/标志位时非常节省内存(一个变量就可以存 32 种布尔状态)。
- 缺点:可读性差(容易出现魔法数字),如果是业务代码,必须配上完善的注释;存在 32 位整数上限陷阱。
- 面试如何应对:
- 面试官问到时,先讲 Vue/React 源码中的权限/状态标志位 (
Flags) 的应用,最能体现你的技术深度。 - 如果让你写防抖节流或者某些算法,顺手用个
~~或者| 0来做数学取整,能侧面秀一下基本功。 - 不要在普通业务代码里滥用,代码的可维护性往往比压榨那微秒级的性能更重要。
- 面试官问到时,先讲 Vue/React 源码中的权限/状态标志位 (