开篇:编程里的「隐藏技能」,关键时刻能救场
想象你学完基础运算符后,能熟练做计算、判断和赋值,但总感觉自己像刚学会用菜刀切菜的厨师——能完成基本操作,却没见识过更高级的厨具(比如雕刻刀、压面机)。今天我们要聊的位运算符和一元运算符,就是JavaScript里的「高级厨具」。它们虽然不常用,但在特定场景下能大幅提升效率,甚至解决一些棘手问题。
一、位运算符:和二进制「打交道」的魔法
位运算符的名字听起来很「硬核」,但本质上是一组直接操作数值二进制位(0和1)的工具。计算机底层所有数据最终都会转成二进制,因此位运算相当于直接和计算机的「母语」对话。
1. 为什么需要位运算?
举个生活例子:你想设计一个「权限系统」,用户可能有「读」「写」「删除」三种权限。用普通变量存储可能需要多个布尔值(如 canRead: true、canWrite: false),但如果用二进制位,一个数字就能搞定:
- 读权限:
001(二进制)→ 十进制1 - 写权限:
010(二进制)→ 十进制2 - 删除权限:
100(二进制)→ 十进制4
用户有「读+写」权限就是 001 | 010 = 011(二进制)→ 十进制3。这种用二进制位组合信息的方式,就是位运算的典型应用。
2. 常见的位运算符(附生活类比)
JavaScript提供了7种位运算符,我们用「灯的开关」(0=关,1=开)来类比理解:
| 运算符 | 名称 | 作用(对两个数的每一位操作) | 生活类比 | 示例(十进制转二进制) |
|---|---|---|---|---|
& | 按位与 | 两位都为1时结果为1,否则为0 | 两个开关都打开,灯才亮 | 3 & 5 → 011 & 101 = 001 → 1 |
| ` | ` | 按位或 | 至少一位为1时结果为1 | 至少一个开关打开,灯就亮 |
^ | 按位异或 | 两位不同时结果为1,相同时为0 | 两个开关状态不同,灯才亮 | 3 ^ 5 → 011 ^ 101 = 110 → 6 |
~ | 按位非 | 单目运算符,将每一位取反(0变1,1变0) | 把灯的状态完全反转(开→关,关→开) | ~3 → ~00000011 = 11111100 → -4(补码表示) |
<< | 左移 | 将二进制位向左移动n位,右侧补0 | 把灯的数量翻倍(左移1位=×2) | 3 << 1 → 0011 → 0110 → 6(3×2=6) |
>> | 右移(带符号) | 将二进制位向右移动n位,左侧补符号位(正数补0,负数补1) | 把灯的数量减半(右移1位=÷2取整) | 6 >> 1 → 0110 → 0011 → 3(6÷2=3) |
>>> | 无符号右移 | 向右移动n位,左侧补0(无论正负) | 强制把数当正数处理后减半 | -6 >>> 1 → 11111010 → 01111010 → 122(特殊场景用) |
3. 实际应用场景
场景1:快速计算倍数
左移 << 相当于乘以2的n次方,右移 >> 相当于除以2的n次方(取整)。这在处理二进制数据或性能敏感的场景(如图形计算)中非常高效:
let num = 5;
console.log(num << 2); // 5×2²=20(二进制101→10100)
console.log(num >> 1); // 5÷2=2(二进制101→10)
场景2:权限系统设计
用位掩码组合权限,节省存储空间:
const READ = 1; // 001
const WRITE = 2; // 010
const DELETE = 4; // 100
let userPermissions = READ | WRITE; // 001 | 010 = 011 → 3(有读+写权限)
// 检查是否有写权限:用按位与判断对应位是否为1
console.log((userPermissions & WRITE) !== 0); // true(有写权限)
场景3:判断奇偶性(比取模更快)
一个数是偶数当且仅当二进制最后一位是0,奇数则是1:
let num = 7;
console.log((num & 1) === 0); // false(最后一位是1,奇数)
console.log((num & 1) === 1); // true
二、一元运算符:只操作一个数的「快捷按钮」
一元运算符只需要一个操作数,比如 +5、-3、++count 等。它们看似简单,但有些用法可能让你「大跌眼镜」。
1. 自增/自减:++ 和 -- 的「前置后置」陷阱
我们之前学过 ++(自增)和 --(自减),但它们的「前置」和「后置」写法会导致不同结果,需要重点复习:
| 写法 | 含义 | 示例(初始count=0) |
|---|---|---|
++count | 先自增1,再使用新值 | console.log(++count) → 1(count变为1) |
count++ | 先使用旧值,再自增1 | console.log(count++) → 0(count变为1) |
--count | 先自减1,再使用新值 | console.log(--count) → -1(count变为-1) |
count-- | 先使用旧值,再自减1 | console.log(count--) → 0(count变为-1) |
关键记忆点:
- 前置(
++count):先操作,后使用 → 结果是新值 - 后置(
count++):先使用,后操作 → 结果是旧值
实际应用:
// 循环中使用后置++:先判断条件,再自增
for (let i = 0; i < 3; i++) {
console.log(i); // 输出0, 1, 2(i先参与循环体,再自增)
}
// 前置++的特殊用法:快速跳过某个值
let j = 0;
console.log(++j); // 直接输出1(j变为1)
2. 正负号:+ 和 - 的「隐藏功能」
你可能知道 +5 就是5,-3 就是-3,但它们还有两个隐藏用法:
-
显式转换数字类型:
+可以将字符串或其他类型转为数字(类似Number())console.log(+ "123"); // 123(字符串转数字) console.log(+ true); // 1(布尔值true转1) console.log(+ null); // 0(null转0) console.log(+ "abc"); // NaN(无法转换的字符串转NaN) -
快速取反:
-可以将数字取反,或对非数字类型先转数字再取反console.log(-5); // -5 console.log(- "10"); // -10(字符串转数字后取反) console.log(- true); // -1(布尔值转数字后取反)
3. typeof:查看变量的「类型身份证」(再复习)
typeof 可以返回变量的类型字符串,但需要注意 null 的特殊情况:
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object"(历史bug,需用===null判断)
console.log(typeof { name: "小明" }); // "object"
console.log(typeof function(){}); // "function"(函数是特殊的对象)
4. delete:删除对象的「私有财产」
delete 运算符用于删除对象的自身属性(无法删除继承的属性):
let user = {
name: "小明",
age: 18
};
console.log(user.name); // "小明"(存在name属性)
delete user.name; // 删除name属性
console.log(user.name); // undefined(name属性被删除)
注意事项:
- 无法删除用
var/let/const声明的全局变量(它们属于全局对象的不可配置属性) - 删除数组元素会留下「空洞」,但数组长度不变:
let arr = [1, 2, 3]; delete arr[1]; console.log(arr); // [1, empty, 3] console.log(arr.length); // 3(长度不变)
5. void:执行表达式但「不返回值」
void 运算符会执行后面的表达式,但始终返回 undefined。常见于两种场景:
-
阻止默认行为(早期JavaScript中):
<a href="https://example.com" onclick="void alert('点击了链接')">点击我</a> <!-- 点击后弹出提示,但不会跳转页面 --> -
立即执行函数表达式(IIFE):
void function() { console.log("这个函数执行了,但返回值被忽略"); }();
三、逗号运算符:一行代码的「多任务处理」
逗号运算符 , 可以在一行中执行多个表达式,返回最后一个表达式的结果(前面的表达式会被执行,但结果被忽略)。它在需要简化代码或循环中同时操作多个变量时很有用。
1. 基础用法:执行多个操作
let a = 1;
let b = 2;
let c = (a++, b++, a + b); // 先执行a++(a=2),再执行b++(b=3),最后计算a+b=5
console.log(c); // 输出:5
2. 循环中的「隐藏技能」
在 for 循环中,逗号运算符可以同时初始化或更新多个变量:
// 同时遍历数组的索引和值(旧写法)
let arr = ["a", "b", "c"];
for (let i = 0, j = arr.length - 1; i <= j; i++, j--) {
console.log(`索引${i}和${j}的值:${arr[i]}, ${arr[j]}`);
}
// 输出:
// 索引0和2的值:a, c
// 索引1和1的值:b, b
四、常见陷阱:这些坑别踩!
1. 位运算符的「32位限制」
JavaScript中的位运算会将操作数转换为32位有符号整数(超出部分会被截断):
let bigNum = 0x123456789; // 十进制8198552921(超过32位)
console.log(bigNum << 1); // 结果为-1431655766(截断为32位后计算)
2. delete 无法删除继承属性
let obj = Object.create({ x: 10 }); // obj继承自{x:10}
delete obj.x; // 无法删除继承的x属性
console.log(obj.x); // 10(仍然存在)
3. 逗号运算符的「优先级最低」
逗号运算符的优先级比大多数运算符低,使用时需加括号避免错误:
let x = (1 + 2, 3 + 4); // 正确:先算1+2=3,再算3+4=7 → x=7
let y = 1 + 2, 3 + 4; // 错误:等价于 (let y=1+2), (3+4) → 语法错误
五、练习时间:动手试试冷门运算符
-
基础题:用位运算符判断一个数是否是2的幂(2的幂的二进制只有最高位是1,如2→10,4→100)
function isPowerOfTwo(n) { // 你的代码:n & (n-1) 会消去最后一位1,若结果为0则是2的幂(n>0) return n > 0 && (n & (n - 1)) === 0; } console.log(isPowerOfTwo(8)); // 应该输出true(8是2³) console.log(isPowerOfTwo(6)); // 应该输出false(6不是2的幂) -
进阶题:用
delete删除对象属性,并验证是否删除成功let car = { brand: "特斯拉", price: 1000000 }; delete car.price; console.log(car.price); // 输出:undefined(price属性被删除) -
陷阱题:预测下面代码的输出结果
let x = 5; let y = (x = 10, x + 5); // 先执行x=10(x变为10),再计算10+5=15 console.log(x, y); // 输出:10 15
总结:冷门运算符的核心价值
| 技能点 | 掌握程度检查 |
|---|---|
| 位运算符 | 知道 &、` |
| 一元运算符 | 理解 ++/-- 的前置后置区别,会用 typeof 和 delete |
| 逗号运算符 | 知道其返回最后一个表达式的特性,能在循环中简化代码 |
最后提醒:这些运算符虽然「冷门」,但面试中可能被问到(比如判断奇偶、权限设计)。建议先掌握基础运算符,再回头学习这些进阶内容。遇到具体问题(比如「如何用位运算优化代码」)时,再深入研究会更高效。
如果对某个运算符想深入了解,欢迎随时提问!比如:“位运算符在Node.js中处理Buffer时怎么用?” 或 “delete和delete操作符有什么区别?”
💬 评论
加载中...