位运算符
位运算符
位运算符用于操作数字的二进制表示。本文将介绍JavaScript中的位运算符,包括按位与、或、非、异或以及移位运算符,以及它们的实际应用场景。
JavaScript中的数字表示
在深入位运算符之前,需要了解JavaScript中的数字是如何在内存中表示的:
- JavaScript中的所有数字都使用64位浮点格式(IEEE 754)存储
- 但位运算符会先将数字转换为32位有符号整数,执行操作后再转换回64位浮点数
- 这意味着位运算只能应用于32位整数范围内的数字(-2^31 到 2^31-1)
// 在JavaScript中,数字的二进制表示
const num = 5; // 二进制:00000000000000000000000000000101
基本位运算符
JavaScript提供了以下位运算符:
运算符 | 名称 | 描述 |
---|---|---|
& | 按位与 | 对应位都为1时结果为1,否则为0 |
| | 按位或 | 对应位至少有一个为1时结果为1,否则为0 |
^ | 按位异或 | 对应位不同时结果为1,相同时为0 |
~ | 按位非 | 反转所有位(0变1,1变0) |
<< | 左移 | 将所有位向左移动指定位数 |
>> | 有符号右移 | 将所有位向右移动指定位数,保留符号位 |
>>> | 无符号右移 | 将所有位向右移动指定位数,符号位也移动 |
按位与运算符 (&)
按位与运算符对两个操作数的每一位执行与操作。只有当两个操作数的对应位都为1时,结果的对应位才为1,否则为0。
const a = 5; // 二进制:00000000000000000000000000000101
const b = 3; // 二进制:00000000000000000000000000000011
const result = a & b; // 二进制:00000000000000000000000000000001 (十进制:1)
console.log(result); // 输出:1
应用场景
检查奇偶性:
function isEven(num) { return (num & 1) === 0; // 如果最低位是0,则为偶数 } console.log(isEven(4)); // true console.log(isEven(7)); // false
位掩码(检查特定位):
const FLAG_A = 1; // 二进制:001 const FLAG_B = 2; // 二进制:010 const FLAG_C = 4; // 二进制:100 const flags = FLAG_A | FLAG_C; // 二进制:101 // 检查是否设置了FLAG_A console.log((flags & FLAG_A) !== 0); // true // 检查是否设置了FLAG_B console.log((flags & FLAG_B) !== 0); // false
按位或运算符 (|)
按位或运算符对两个操作数的每一位执行或操作。如果两个操作数的对应位至少有一个为1,则结果的对应位为1,否则为0。
const a = 5; // 二进制:00000000000000000000000000000101
const b = 3; // 二进制:00000000000000000000000000000011
const result = a | b; // 二进制:00000000000000000000000000000111 (十进制:7)
console.log(result); // 输出:7
应用场景
位掩码(设置特定位):
const FLAG_A = 1; // 二进制:001 const FLAG_B = 2; // 二进制:010 const FLAG_C = 4; // 二进制:100 let flags = 0; // 初始没有设置任何标志 // 设置FLAG_A和FLAG_C flags |= FLAG_A; // 现在flags是001 flags |= FLAG_C; // 现在flags是101 console.log(flags); // 5
取整(向下取整):
function floorWithBitwise(num) { return num | 0; // 将小数部分截断 } console.log(floorWithBitwise(3.7)); // 3 console.log(floorWithBitwise(-3.7)); // -3
按位异或运算符 (^)
按位异或运算符对两个操作数的每一位执行异或操作。如果两个操作数的对应位不同,则结果的对应位为1,否则为0。
const a = 5; // 二进制:00000000000000000000000000000101
const b = 3; // 二进制:00000000000000000000000000000011
const result = a ^ b; // 二进制:00000000000000000000000000000110 (十进制:6)
console.log(result); // 输出:6
应用场景
切换标志位:
let flag = true; // 使用异或切换布尔值 flag = !flag; // 传统方式 // 使用位运算(对于数值) let bitFlag = 1; bitFlag ^= 1; // 现在bitFlag是0 bitFlag ^= 1; // 现在bitFlag是1
交换两个变量的值(不使用临时变量):
let a = 5; let b = 9; a ^= b; b ^= a; a ^= b; console.log(a, b); // 9, 5
查找数组中只出现一次的数字:
function findSingle(arr) { return arr.reduce((result, num) => result ^ num, 0); } const nums = [4, 1, 2, 1, 2]; console.log(findSingle(nums)); // 4
按位非运算符 (~)
按位非运算符反转操作数的所有位(0变为1,1变为0)。在JavaScript中,这相当于对数字取反再减1。
const a = 5; // 二进制:00000000000000000000000000000101
const result = ~a; // 二进制:11111111111111111111111111111010 (十进制:-6)
console.log(result); // 输出:-6
应用场景
取反减一:
const a = 5; console.log(~a); // -6 (相当于 -(a+1))
检查数组中是否存在元素:
const arr = ['apple', 'banana', 'orange']; // 传统方式 if (arr.indexOf('banana') !== -1) { console.log('找到了香蕉'); } // 使用按位非 if (~arr.indexOf('banana')) { console.log('找到了香蕉'); // 会执行 } if (~arr.indexOf('grape')) { console.log('找到了葡萄'); // 不会执行 }
左移运算符 (<<)
左移运算符将第一个操作数的所有位向左移动指定的位数。左侧超出的位被丢弃,右侧空出的位用0填充。
const a = 5; // 二进制:00000000000000000000000000000101
const result = a << 2; // 二进制:00000000000000000000000000010100 (十进制:20)
console.log(result); // 输出:20
应用场景
快速乘法(乘以2的幂):
const a = 5; console.log(a << 1); // 10 (相当于 a * 2) console.log(a << 2); // 20 (相当于 a * 4) console.log(a << 3); // 40 (相当于 a * 8)
创建位掩码:
const BIT_1 = 1 << 0; // 1 const BIT_2 = 1 << 1; // 2 const BIT_3 = 1 << 2; // 4 const BIT_4 = 1 << 3; // 8 // 创建包含多个标志的掩码 const MASK = BIT_1 | BIT_3; // 5
有符号右移运算符 (>>)
有符号右移运算符将第一个操作数的所有位向右移动指定的位数。右侧超出的位被丢弃,左侧空出的位用符号位(最高位)填充。
const a = 5; // 二进制:00000000000000000000000000000101
const result = a >> 1; // 二进制:00000000000000000000000000000010 (十进制:2)
const b = -5; // 二进制:11111111111111111111111111111011
const resultB = b >> 1; // 二进制:11111111111111111111111111111101 (十进制:-3)
console.log(result); // 输出:2
console.log(resultB); // 输出:-3
应用场景
快速除法(除以2的幂):
const a = 16; console.log(a >> 1); // 8 (相当于 Math.floor(a / 2)) console.log(a >> 2); // 4 (相当于 Math.floor(a / 4)) console.log(a >> 3); // 2 (相当于 Math.floor(a / 8)) const b = -16; console.log(b >> 1); // -8 (相当于 Math.ceil(b / 2))
RGB颜色值提取:
const rgb = 0xFF00CC; // 紫色 const r = (rgb >> 16) & 0xFF; // 255 const g = (rgb >> 8) & 0xFF; // 0 const b = rgb & 0xFF; // 204 console.log(`R: ${r}, G: ${g}, B: ${b}`); // R: 255, G: 0, B: 204
无符号右移运算符 (>>>)
无符号右移运算符将第一个操作数的所有位向右移动指定的位数。右侧超出的位被丢弃,左侧空出的位用0填充,不考虑符号位。
const a = 5; // 二进制:00000000000000000000000000000101
const result = a >>> 1; // 二进制:00000000000000000000000000000010 (十进制:2)
const b = -5; // 二进制:11111111111111111111111111111011
const resultB = b >>> 1; // 二进制:01111111111111111111111111111101 (十进制:2147483645)
console.log(result); // 输出:2
console.log(resultB); // 输出:2147483645
应用场景
处理无符号整数:
const signedInt = -1; const unsignedInt = signedInt >>> 0; // 4294967295 console.log(unsignedInt.toString(16)); // "ffffffff"
快速计算正数除以2的幂:
const a = 16; console.log(a >>> 1); // 8 (对于正数,与>>相同) console.log(a >>> 2); // 4
位运算符的实际应用
1. 权限系统
使用位掩码实现简单的权限系统:
// 定义权限
const READ = 1; // 001
const WRITE = 2; // 010
const EXECUTE = 4; // 100
// 授予权限
function grantPermission(currentPermissions, permissionToGrant) {
return currentPermissions | permissionToGrant;
}
// 撤销权限
function revokePermission(currentPermissions, permissionToRevoke) {
return currentPermissions & ~permissionToRevoke;
}
// 检查权限
function hasPermission(currentPermissions, permissionToCheck) {
return (currentPermissions & permissionToCheck) === permissionToCheck;
}
// 使用示例
let userPermissions = 0; // 初始没有权限
// 授予读和写权限
userPermissions = grantPermission(userPermissions, READ | WRITE);
console.log(userPermissions.toString(2)); // "11"
// 检查权限
console.log(hasPermission(userPermissions, READ)); // true
console.log(hasPermission(userPermissions, EXECUTE)); // false
// 撤销写权限
userPermissions = revokePermission(userPermissions, WRITE);
console.log(userPermissions.toString(2)); // "1"
console.log(hasPermission(userPermissions, WRITE)); // false
2. 颜色处理
使用位运算符处理RGB颜色:
// 将RGB分量合并为一个颜色值
function rgbToHex(r, g, b) {
return ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
// 从颜色值提取RGB分量
function hexToRgb(hex) {
return {
r: (hex >> 16) & 0xFF,
g: (hex >> 8) & 0xFF,
b: hex & 0xFF
};
}
// 使用示例
const color = rgbToHex(255, 100, 50);
console.log(color.toString(16)); // "ff6432"
const rgb = hexToRgb(0xff6432);
console.log(rgb); // { r: 255, g: 100, b: 50 }
3. 高效数据结构
使用位运算实现高效的数据结构,如位集合(Bitset):
class Bitset {
constructor(size) {
this.data = new Array(Math.ceil(size / 32)).fill(0);
this.size = size;
}
// 设置指定位
set(position) {
if (position < 0 || position >= this.size) {
throw new Error("位置超出范围");
}
const index = Math.floor(position / 32);
const bitPosition = position % 32;
this.data[index] |= (1 << bitPosition);
}
// 清除指定位
clear(position) {
if (position < 0 || position >= this.size) {
throw new Error("位置超出范围");
}
const index = Math.floor(position / 32);
const bitPosition = position % 32;
this.data[index] &= ~(1 << bitPosition);
}
// 检查指定位是否设置
test(position) {
if (position < 0 || position >= this.size) {
throw new Error("位置超出范围");
}
const index = Math.floor(position / 32);
const bitPosition = position % 32;
return (this.data[index] & (1 << bitPosition)) !== 0;
}
// 切换指定位的状态
toggle(position) {
if (position < 0 || position >= this.size) {
throw new Error("位置超出范围");
}
const index = Math.floor(position / 32);
const bitPosition = position % 32;
this.data[index] ^= (1 << bitPosition);
}
}
// 使用示例
const bs = new Bitset(100);
bs.set(5);
bs.set(10);
console.log(bs.test(5)); // true
console.log(bs.test(6)); // false
bs.toggle(5);
console.log(bs.test(5)); // false
4. 加密和哈希
简单的异或加密:
function xorEncrypt(text, key) {
let result = '';
for (let i = 0; i < text.length; i++) {
// 对每个字符的ASCII码与密钥进行异或操作
const charCode = text.charCodeAt(i) ^ key;
result += String.fromCharCode(charCode);
}
return result;
}
function xorDecrypt(encryptedText, key) {
// 异或操作的特性:a ^ b ^ b = a
return xorEncrypt(encryptedText, key);
}
// 使用示例
const message = "Hello, World!";
const key = 42;
const encrypted = xorEncrypt(message, key);
console.log(encrypted); // 加密后的文本
const decrypted = xorDecrypt(encrypted, key);
console.log(decrypted); // "Hello, World!"
5. 游戏开发
在游戏开发中使用位运算处理游戏状态:
// 游戏状态标志
const PLAYER_ALIVE = 1 << 0; // 0001
const PLAYER_MOVING = 1 << 1; // 0010
const PLAYER_JUMPING = 1 << 2; // 0100
const PLAYER_SHOOTING = 1 << 3; // 1000
class Player {
constructor() {
this.state = PLAYER_ALIVE; // 初始状态:活着
}
startMoving() {
this.state |= PLAYER_MOVING;
}
stopMoving() {
this.state &= ~PLAYER_MOVING;
}
jump() {
if (!(this.state & PLAYER_JUMPING)) {
this.state |= PLAYER_JUMPING;
// 跳跃逻辑...
}
}
land() {
this.state &= ~PLAYER_JUMPING;
}
shoot() {
this.state |= PLAYER_SHOOTING;
// 射击逻辑...
// 射击是瞬时动作,立即重置状态
setTimeout(() => {
this.state &= ~PLAYER_SHOOTING;
}, 100);
}
die() {
this.state &= ~PLAYER_ALIVE;
// 死亡逻辑...
}
isAlive() {
return (this.state & PLAYER_ALIVE) !== 0;
}
isMoving() {
return (this.state & PLAYER_MOVING) !== 0;
}
isJumping() {
return (this.state & PLAYER_JUMPING) !== 0;
}
isShooting() {
return (this.state & PLAYER_SHOOTING) !== 0;
}
}
// 使用示例
const player = new Player();
player.startMoving();
player.jump();
console.log(player.isMoving()); // true
console.log(player.isJumping()); // true
console.log(player.state.toString(2)); // "111" (活着、移动中、跳跃中)
位运算符的性能考虑
位运算通常比等效的算术运算更快,特别是在以下情况:
取模运算:
x & (y - 1)
比x % y
更快(当y是2的幂时)// 慢 const remainder = x % 8; // 快 const remainder = x & 7; // 7 = 8 - 1
乘除法:使用左移和右移代替乘以或除以2的幂
// 慢 const product = x * 4; const quotient = x / 4; // 快 const product = x << 2; const quotient = x >> 2;
取整:使用位运算代替Math.floor()(对于32位整数范围内的数字)
// 慢 const integer = Math.floor(x); // 快 const integer = x | 0;
然而,需要注意的是:
- 现代JavaScript引擎已经高度优化,性能差异可能不明显
- 位运算可能降低代码可读性
- 位运算只适用于32位整数范围内的数字
常见错误和最佳实践
常见错误
忽略32位整数限制:
// 错误 const largeNumber = 1 << 31; // -2147483648(符号位被设置) // 正确 const largeNumber = 1 * Math.pow(2, 31); // 2147483648
忽略负数的二进制表示:
// 可能导致意外结果 const negativeShift = -5 >> 1; // -3,而不是-2.5
过度使用位运算降低可读性:
// 难以理解 const result = (x & 0xFF) | ((y & 0xFF) << 8) | ((z & 0x0F) << 16); // 更清晰 const result = (x & 0xFF) | // 低8位 ((y & 0xFF) << 8) | // 中8位 ((z & 0x0F) << 16); // 高4位
最佳实践
使用命名常量提高可读性:
// 不推荐 if (permissions & 4) { // ... } // 推荐 const EXECUTE_PERMISSION = 4; if (permissions & EXECUTE_PERMISSION) { // ... }
使用位运算的辅助函数:
function setBit(num, position) { return num | (1 << position); } function clearBit(num, position) { return num & ~(1 << position); } function toggleBit(num, position) { return num ^ (1 << position); } function testBit(num, position) { return (num & (1 << position)) !== 0; }
使用toString(2)调试二进制值:
const num = 42; console.log(num.toString(2)); // "101010" // 格式化为固定长度的二进制字符串 function formatBinary(num, length = 8) { return (num >>> 0).toString(2).padStart(length, '0'); } console.log(formatBinary(42, 8)); // "00101010"
避免在关键性能代码之外过度优化:
// 除非在性能关键部分,否则优先考虑可读性 const isEven = num % 2 === 0; // 比 (num & 1) === 0 更易读
总结
JavaScript的位运算符提供了直接操作数字二进制表示的能力。虽然在日常编程中使用频率不高,但在特定场景下,如权限管理、性能优化、数据压缩等,位运算可以提供简洁高效的解决方案。
主要位运算符包括:
- 按位与(&):对应位都为1时结果为1
- 按位或(|):对应位至少有一个为1时结果为1
- 按位异或(^):对应位不同时结果为1
- 按位非(~):反转所有位
- 左移(<<):向左移动位
- 有符号右移(>>):向右移动位,保留符号位
- 无符号右移(>>>):向右移动位,不保留符号位
使用位运算时,需要注意JavaScript中的数字表示限制,以及可能的可读性问题。在适当的场景下合理使用位运算,可以编写出更高效、更优雅的代码。
练习
编写一个函数,使用位运算计算一个整数中二进制表示中1的个数。
实现一个函数,判断一个数是否是2的幂。
创建一个简单的颜色混合函数,使用位运算混合两个RGB颜色。
编写一个函数,使用位运算交换两个整数的值,不使用临时变量。
实现一个简单的位图数据结构,可以高效地存储和查询布尔值数组。