位运算
问题
JavaScript 中有哪些位运算符?位运算有什么实际应用场景?
答案
位运算直接操作数字的二进制位,在权限控制、性能优化、算法题中有广泛应用。JavaScript 中位运算会将操作数转换为 32 位有符号整数。
位运算符
| 运算符 | 名称 | 说明 |
|---|---|---|
& | 按位与 | 两位都为 1 才为 1 |
| | 按位或 | 有一位为 1 就为 1 |
^ | 按位异或 | 两位不同才为 1 |
~ | 按位非 | 取反 |
<< | 左移 | 向左移动指定位数 |
>> | 有符号右移 | 向右移动,保留符号位 |
>>> | 无符号右移 | 向右移动,补 0 |
基础示例
按位与 (&)
// 两位都为 1 才为 1
// 5: 0101
// & 3: 0011
// -----
// 1: 0001
console.log(5 & 3); // 1
// 实际应用:判断奇偶
function isOdd(n: number): boolean {
return (n & 1) === 1;
}
console.log(isOdd(5)); // true
console.log(isOdd(4)); // false
按位或 (|)
// 有一位为 1 就为 1
// 5: 0101
// | 3: 0011
// -----
// 7: 0111
console.log(5 | 3); // 7
// 实际应用:向下取整
console.log(3.7 | 0); // 3
console.log(-3.7 | 0); // -3
按位异或 (^)
// 两位不同才为 1
// 5: 0101
// ^ 3: 0011
// -----
// 6: 0110
console.log(5 ^ 3); // 6
// 特性:a ^ a = 0, a ^ 0 = a
console.log(5 ^ 5); // 0
console.log(5 ^ 0); // 5
// 实际应用:交换变量
let a = 5, b = 3;
a = a ^ b; // a = 6
b = a ^ b; // b = 5
a = a ^ b; // a = 3
console.log(a, b); // 3, 5
按位非 (~)
// 取反:~n = -(n + 1)
console.log(~5); // -6
console.log(~-5); // 4
console.log(~0); // -1
// 实际应用:判断 indexOf 结果
const arr = [1, 2, 3];
if (~arr.indexOf(2)) {
console.log('found'); // ~1 = -2, 为真
}
if (~arr.indexOf(5)) {
// 不执行,~(-1) = 0, 为假
}
// 双重取反实现向下取整
console.log(~~3.7); // 3
console.log(~~-3.7); // -3
左移 (<<)
// 向左移动,右边补 0
// 5: 00000101
// 5 << 1: 00001010 = 10
console.log(5 << 1); // 10
console.log(5 << 2); // 20
// 相当于乘以 2 的 n 次方
console.log(5 << 1); // 5 * 2 = 10
console.log(5 << 3); // 5 * 8 = 40
右移 (>> 和 >>>)
// 有符号右移:保留符号位
console.log(10 >> 1); // 5
console.log(-10 >> 1); // -5
// 无符号右移:补 0
console.log(10 >>> 1); // 5
console.log(-10 >>> 1); // 2147483643(变成很大的正数)
// 相当于除以 2 的 n 次方(向下取整)
console.log(10 >> 1); // 10 / 2 = 5
console.log(10 >> 2); // 10 / 4 = 2
实际应用
权限控制
// 使用位标志表示权限
const Permission = {
READ: 1, // 0001
WRITE: 2, // 0010
EXECUTE: 4, // 0100
DELETE: 8 // 1000
} as const;
type PermissionType = typeof Permission[keyof typeof Permission];
class User {
private permissions: number = 0;
// 添加权限
addPermission(permission: PermissionType): void {
this.permissions |= permission;
}
// 移除权限
removePermission(permission: PermissionType): void {
this.permissions &= ~permission;
}
// 检查权限
hasPermission(permission: PermissionType): boolean {
return (this.permissions & permission) === permission;
}
// 切换权限
togglePermission(permission: PermissionType): void {
this.permissions ^= permission;
}
}
const user = new User();
user.addPermission(Permission.READ);
user.addPermission(Permission.WRITE);
console.log(user.hasPermission(Permission.READ)); // true
console.log(user.hasPermission(Permission.DELETE)); // false
user.removePermission(Permission.WRITE);
console.log(user.hasPermission(Permission.WRITE)); // false
状态管理
// React 中的 Fiber flags
const NoFlags = 0b0000000000000000000;
const Placement = 0b0000000000000000010;
const Update = 0b0000000000000000100;
const Deletion = 0b0000000000000001000;
interface Fiber {
flags: number;
}
function markUpdate(fiber: Fiber): void {
fiber.flags |= Update;
}
function hasPlacement(fiber: Fiber): boolean {
return (fiber.flags & Placement) !== NoFlags;
}
颜色处理
// RGB 颜色处理
function rgbToHex(r: number, g: number, b: number): string {
// 合并为一个数字
const color = (r << 16) | (g << 8) | b;
return '#' + color.toString(16).padStart(6, '0');
}
function hexToRgb(hex: string): { r: number; g: number; b: number } {
const color = parseInt(hex.slice(1), 16);
return {
r: (color >> 16) & 0xff,
g: (color >> 8) & 0xff,
b: color & 0xff
};
}
console.log(rgbToHex(255, 128, 64)); // '#ff8040'
console.log(hexToRgb('#ff8040')); // { r: 255, g: 128, b: 64 }
开关/标志管理
// 管理多个布尔开关
const Options = {
BOLD: 1 << 0, // 1
ITALIC: 1 << 1, // 2
UNDERLINE: 1 << 2, // 4
STRIKETHROUGH: 1 << 3 // 8
} as const;
class TextStyle {
private flags = 0;
setBold(enabled: boolean): void {
this.flags = enabled
? this.flags | Options.BOLD
: this.flags & ~Options.BOLD;
}
isBold(): boolean {
return (this.flags & Options.BOLD) !== 0;
}
setOptions(options: number): void {
this.flags = options;
}
getOptions(): number {
return this.flags;
}
}
const style = new TextStyle();
style.setOptions(Options.BOLD | Options.ITALIC);
console.log(style.isBold()); // true
算法应用
// 1. 判断是否是 2 的幂
function isPowerOfTwo(n: number): boolean {
return n > 0 && (n & (n - 1)) === 0;
}
console.log(isPowerOfTwo(8)); // true (1000 & 0111 = 0)
console.log(isPowerOfTwo(6)); // false
// 2. 找出只出现一次的数字
function singleNumber(nums: number[]): number {
return nums.reduce((acc, num) => acc ^ num, 0);
}
console.log(singleNumber([4, 1, 2, 1, 2])); // 4
// 3. 统计 1 的个数(汉明权重)
function hammingWeight(n: number): number {
let count = 0;
while (n !== 0) {
count += n & 1;
n >>>= 1;
}
return count;
}
// 或者用 n & (n-1) 消除最后一个 1
function hammingWeight2(n: number): number {
let count = 0;
while (n !== 0) {
n &= n - 1;
count++;
}
return count;
}
console.log(hammingWeight(11)); // 3 (1011 有 3 个 1)
// 4. 汉明距离(两数二进制不同的位数)
function hammingDistance(x: number, y: number): number {
return hammingWeight(x ^ y);
}
console.log(hammingDistance(1, 4)); // 2 (001 ^ 100 = 101)
// 5. 反转二进制
function reverseBits(n: number): number {
let result = 0;
for (let i = 0; i < 32; i++) {
result = (result << 1) | (n & 1);
n >>>= 1;
}
return result >>> 0; // 转为无符号
}
性能优化
// 1. 快速乘除 2 的幂
const n = 10;
n << 1; // n * 2 = 20
n >> 1; // n / 2 = 5
n << 3; // n * 8 = 80
n >> 2; // n / 4 = 2
// 2. 快速取整
const float = 3.7;
float | 0; // 3
~~float; // 3
float >> 0; // 3
// 3. 取模运算(除数是 2 的幂)
const num = 15;
num % 8; // 7
num & 7; // 7(更快)
// 4. 判断奇偶
num & 1; // 1 表示奇数,0 表示偶数
位运算技巧总结
| 操作 | 表达式 | 说明 |
|---|---|---|
| 判断奇偶 | n & 1 | 1 为奇,0 为偶 |
| 乘以 2 | n << 1 | |
| 除以 2 | n >> 1 | 向下取整 |
| 取整 | n | 0 或 ~~n | |
| 交换变量 | a ^= b; b ^= a; a ^= b | |
| 取反 | ~n + 1 或 -n | 补码 |
| 清除最后一个 1 | n & (n - 1) | |
| 获取最后一个 1 | n & (-n) | |
| 判断 2 的幂 | (n & (n - 1)) === 0 | |
| 设置第 i 位为 1 | n | (1 << i) | |
| 设置第 i 位为 0 | n & ~(1 << i) | |
| 切换第 i 位 | n ^ (1 << i) | |
| 获取第 i 位 | (n >> i) & 1 |
常见面试问题
Q1: 不使用临时变量交换两个数?
答案:
// 方法 1: 异或
let a = 5, b = 3;
a = a ^ b;
b = a ^ b;
a = a ^ b;
console.log(a, b); // 3, 5
// 方法 2: 加减法
a = a + b;
b = a - b;
a = a - b;
// 方法 3: 解构(推荐)
[a, b] = [b, a];
Q2: 找出数组中只出现一次的数?
答案:
// 其他数都出现两次
function singleNumber(nums: number[]): number {
return nums.reduce((a, b) => a ^ b, 0);
}
// 原理:a ^ a = 0, a ^ 0 = a
// [4, 1, 2, 1, 2]
// 4 ^ 1 ^ 2 ^ 1 ^ 2 = 4 ^ (1 ^ 1) ^ (2 ^ 2) = 4 ^ 0 ^ 0 = 4
Q3: 如何判断一个数是 2 的幂?
答案:
function isPowerOfTwo(n: number): boolean {
return n > 0 && (n & (n - 1)) === 0;
}
// 原理:2 的幂的二进制只有一个 1
// 8 = 1000
// 7 = 0111
// 8 & 7 = 0000 = 0
Q4: 位运算实现加法?
答案:
function add(a: number, b: number): number {
while (b !== 0) {
const carry = (a & b) << 1; // 进位
a = a ^ b; // 不进位加法
b = carry;
}
return a;
}
console.log(add(5, 3)); // 8
Q5: 位运算为什么比算术运算快?
答案:
- CPU 直接支持:位运算是 CPU 的基本指令,一个时钟周期完成
- 无需转换:数据本就是二进制存储,无需额外转换
- 简单操作:只需要简单的逻辑门运算
但要注意:
- 现代 JavaScript 引擎已高度优化,差异不明显
- 可读性更重要,除非有明确的性能需求
- 位运算仅适用于 32 位整数