人人都会AI编程

8.5 运算符与表达式:算术、比较、逻辑、位运算、三目运算

更新时间:2026-07-11

程序本质上是对数据的运算与流程控制,而运算通过运算符(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”的值有:false0""nullundefinedNaN,其余均视为 true。

短路求值的实际应用

设置默认值(在 ES2020 可选链和空值合并运算符出现前非常常用):

let name = userInput || "Guest";  // 若 userInput 为空则用 "Guest"
let loggedIn = isLogged && user.name; // 若已登录则获取用户名,否则返回 false

现代 JavaScript 提供了 ??(空值合并运算符)专门处理 nullundefined,比 || 更精确,因为 || 会把 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.143.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);

对于复杂的表达式,拆分成多行并用临时变量,远比一行浓缩的公式更利于维护。

熟练掌握这些运算符,并结合前面讨论过的隐式类型转换规则,你就能写出简洁且行为可预测的代码。记住,代码的首要读者是人而不是引擎,清晰表达意图永远胜于炫技。