程序本质上是对数据的运算与流程控制,而运算通过运算符(Operators)组织成表达式(Expressions)完成。JavaScript 提供了丰富的运算符,理解和善用它们能让你写出更简洁、更高效的代码。本节聚焦最常用的五种运算符分类,并特别标注易混淆的坑点。
8.5.1 算术运算符
算术运算符用于执行基础数学运算,包括加减乘除、取余、指数等。
let sum = 5 + 3; // 8
let diff = 10 - 2; // 8
let product = 4 * 2; // 8
let quotient = 16 / 2; // 8
let remainder = 10 % 4; // 2 (取余数)
let power = 2 ** 3; // 8 (指数,ES2016 引入)
递增/递减(++ / --)
有前置和后置两种形式,区别在于返回值是自增前还是自增后的值。
let a = 5;
let b = a++; // b = 5, a = 6 (先返回 a,再自增)
let c = 10;
let d = --c; // d = 9, c = 9 (先自减,再返回 c)
加法运算符的“双面性”
+ 在 JavaScript 中同时扮演数字加法和字符串拼接两个角色,这是最常见的隐式转换来源:
let mixed = 1 + "2"; // "12" (数字转为字符串)
let numeric = 1 + 2 + "3"; // "33" (从左到右,先做加法得到3,再与字符串拼接)
如果参与运算的任意一方是字符串,+ 就会执行字符串拼接;否则为数字加法。其他算术运算符(-、*、/、%)则一律尝试将操作数转为数字:
"6" / "2"; // 3
"6" - "2"; // 4
"hello" * 2; // NaN
实用建议
- 做数值运算前,用
Number()或parseFloat()/parseInt()显式转换,避免意外拼接。 - 字符串拼接优先使用模板字符串(
),更清晰易读。
8.5.2 比较运算符
比较运算符返回布尔值,是条件判断的基础。
宽松相等(==)与严格相等(===)
| 运算符 | 名称 | 规则 |
|--------|------|------|
| == | 相等 | 类型不同时会尝试类型转换再比较 |
| != | 不等 | 同上 |
| === | 严格相等 | 既比较值又比较类型,类型不同直接返回 false |
| !== | 严格不等 | 同上 |
0 == "0"; // true (字符串 "0" 被转为数字 0)
0 == false; // true (false 转为数字 0)
" " == 0; // true (空白字符串转为 0)
null == undefined; // true (特例,二者宽松相等但不与其他任何值相等)
0 === "0"; // false
0 === false; // false
null === undefined; // false
核心原则:永远优先使用 === 和 !==,除非你明确需要类型转换带来的比较结果(几乎不会)。
大小比较
>、<、>=、<= 对不同类型的处理规则与 == 类似,但更复杂。如果比较的两边都是字符串,则按 Unicode 编码顺序逐位比较;否则尽量转为数字进行比较。
"apple" > "banana"; // false (按字符顺序)
"2" > "10"; // true (字符串比较,字符"2" > "1")
2 > 10; // false (数字比较)
NaN 与其他值
NaN 与任何值(包括它自己)都不相等:
NaN == NaN; // false
NaN === NaN; // false
判断一个值是否为 NaN 必须使用 Number.isNaN() 或者利用特性:x !== x 仅当 x 为 NaN 时为 true。
引用类型比较
对象(包括数组、函数)的比较是比较引用地址,而非内部结构:
[1, 2] === [1, 2]; // false (两个不同的数组对象)
{ a: 1 } === { a: 1 }; // false
要比较内容,需要用专门的函数(如 lodash 的 isEqual)或自行递归比较。
8.5.3 逻辑运算符
逻辑运算符主要用于布尔运算,但 JavaScript 中的逻辑运算符能处理任意类型的操作数,并利用了短路求值特性。
与(&&)、或(||)、非(!)
expr1 && expr2:如果expr1能转为false,则返回expr1;否则返回expr2。expr1 || expr2:如果expr1能转为true,则返回expr1;否则返回expr2。!expr:强制转为布尔值后取反。
这里的“能转为 false”的值有:false、0、""、null、undefined、NaN,其余均视为 true。
短路求值的实际应用
设置默认值(在 ES2020 可选链和空值合并运算符出现前非常常用):
let name = userInput || "Guest"; // 若 userInput 为空则用 "Guest"
let loggedIn = isLogged && user.name; // 若已登录则获取用户名,否则返回 false
现代 JavaScript 提供了 ??(空值合并运算符)专门处理 null 和 undefined,比 || 更精确,因为 || 会把 0、"" 也当作假值。
let age = user.age ?? 18; // 仅在 age 为 null/undefined 时使用默认值
条件执行:
condition && doSomething(); // 条件成立才执行函数
注意短路行为可以用于简化代码,但过度使用会降低可读性,复杂逻辑仍建议使用 if 语句。
逻辑非与双重非
!! 可以将任何值转为对应的布尔值,等同于 Boolean():
!!"hello"; // true
!!0; // false
8.5.4 位运算符
位运算符直接操作 32 位有符号整数的比特位,速度很快但可读性较差,通常用于底层运算或特殊技巧。
| 运算符 | 名称 | 示例 | 说明 |
|--------|--------|--------------|------|
| & | 按位与 | 5 & 3 → 1 | 对应位都为 1 才得 1 |
| \| | 按位或 | 5 \| 3 → 7 | 任意一位为 1 就得 1 |
| ^ | 按位异或 | 5 ^ 3 → 6 | 对应位不同则得 1 |
| ~ | 按位非 | ~5 → -6 | 每位取反(包括符号位),等价于 -x-1 |
| << | 左移 | 5 << 1 → 10 | 左移 n 位相当于乘以 2 的 n 次方 |
| >> | 有符号右移 | -8 >> 2 → -2 | 右移保留符号位 |
| >>> | 无符号右移 | -8 >>> 2 → 1073741822 | 右移后用 0 填充高位 |
实际使用场景
- 取整:
~~3.14或3.14 | 0得到 3(只对 32 位整数有效,超过范围会溢出)。 - 奇偶判断:
num & 1,结果为 1 是奇数,0 是偶数。 - 权限管理:用二进制位表示权限开关,通过位掩码检测和组合权限。
- RGB 色彩分量提取:
let red = color >> 16 & 0xFF;
注意:位运算会先将操作数转为 32 位整数,因此对小数、极大数、NaN 等存在精度和溢出问题,绝大多数业务代码并不需要位运算,为了可读性应优先使用算术或逻辑操作。
8.5.5 三目运算符(条件运算符)
condition ? exprIfTrue : exprIfFalse 是 JavaScript 中唯一的三元运算符,用于简洁地表达条件赋值或返回。
let status = age >= 18 ? "成人" : "未成年";
let displayName = user.nickname ? user.nickname : user.username;
链式三目
多个条件可以串联,但超过两次嵌套就会显著降低可读性,建议改用 if-else 或拆分变量。
let category = score >= 90 ? "优秀" :
score >= 60 ? "及格" :
"不及格";
8.5.6 运算符优先级与最佳实践
当多种运算符混合时,由优先级决定运算顺序。例如乘法优先于加法,逻辑与优先于逻辑或。但实际开发中,不要依赖记忆或猜测优先级,直接使用括号 () 明确意图,是最稳妥的做法:
let result = (a + b) * c; // 先加后乘
let isAdmin = (role === "admin") || (role === "root" && isActive);
对于复杂的表达式,拆分成多行并用临时变量,远比一行浓缩的公式更利于维护。
熟练掌握这些运算符,并结合前面讨论过的隐式类型转换规则,你就能写出简洁且行为可预测的代码。记住,代码的首要读者是人而不是引擎,清晰表达意图永远胜于炫技。