跳到主要内容

位运算

问题

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 & 11 为奇,0 为偶
乘以 2n << 1
除以 2n >> 1向下取整
取整n | 0~~n
交换变量a ^= b; b ^= a; a ^= b
取反~n + 1-n补码
清除最后一个 1n & (n - 1)
获取最后一个 1n & (-n)
判断 2 的幂(n & (n - 1)) === 0
设置第 i 位为 1n | (1 << i)
设置第 i 位为 0n & ~(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: 位运算为什么比算术运算快?

答案

  1. CPU 直接支持:位运算是 CPU 的基本指令,一个时钟周期完成
  2. 无需转换:数据本就是二进制存储,无需额外转换
  3. 简单操作:只需要简单的逻辑门运算

但要注意

  • 现代 JavaScript 引擎已高度优化,差异不明显
  • 可读性更重要,除非有明确的性能需求
  • 位运算仅适用于 32 位整数

相关链接