WinddSnow

JavaScript-Operators-and-Expressions

字数统计: 3.7k阅读时长: 16 min
2026/07/30

第46课:运算符与表达式

运算符是 JavaScript 中执行计算、比较、赋值和逻辑判断的符号。表达式是由运算符和操作数组成的代码片段,计算后会产出一个值。理解每一种运算符的语法返回值副作用以及它们之间的优先级和结合性,是写出正确且可预测的 JavaScript 代码的前提。本节课将逐一拆解七大类别运算符的核心行为、隐式类型转换规则以及常见易错点,并通过综合表达式实战来巩固运算符优先级的心智模型。


1. 算术运算符:数值计算与 + 的特殊性

算术运算符接受数值操作数,返回一个数值。除加法外,其余运算符仅用于数值运算。

1.1 六种基础算术运算符

运算符 名称 示例 结果 说明
+ 加法 5 + 3 8 若任一操作数是字符串,则执行字符串拼接(见下文 1.2)。
- 减法 5 - 3 2 非数值操作数会先被转为数值。
* 乘法 5 * 3 15 同上。
/ 除法 6 / 3 2 同上。
% 取余 7 % 3 1 返回除法后的余数,符号与被除数相同。
** 指数 2 ** 3 8 ES2016 引入。等价于 Math.pow(2, 3)。右结合。
1
2
3
4
5
console.log(7 % 3);    // 1
console.log(-7 % 3); // -1(余数符号跟随被除数 -7)
console.log(7 % -3); // 1
console.log(2 ** 3); // 8
console.log(2 ** 3 ** 2); // 512 —— 右结合:先算 3**2=9,再 2**9=512

1.2 加法运算符 + 的字符串拼接行为

+ 是唯一一个重载的算术运算符:只要有一个操作数是字符串,另一个操作数就会被转为字符串,然后执行拼接。

1
2
3
4
5
6
console.log(5 + 3);           // 8(数值加法)
console.log('5' + 3); // '53'(字符串拼接)
console.log(5 + '3'); // '53'
console.log('Hello ' + true); // 'Hello true'
console.log(null + 3); // 3(null 转为 0)
console.log(undefined + 3); // NaN(undefined 转为 NaN)

从左到右的求值顺序

1
2
console.log(1 + 2 + '3'); // '33' —— 先 1+2=3(数值),再 3+'3'='33'
console.log('1' + 2 + 3); // '123' —— '1'+2='12'(字符串),再 '12'+3='123'

1.3 一元 +- 运算符

一元 + 尝试将操作数转为数值,相当于 Number(value)。一元 - 在转换后取负。

1
2
3
4
5
6
7
8
console.log(+'42');     // 42
console.log(+'hello'); // NaN
console.log(+true); // 1
console.log(+null); // 0
console.log(+undefined);// NaN

console.log(-'42'); // -42
console.log(-true); // -1

实用技巧+dateString 是将日期字符串快速转为时间戳的简便写法,但现代代码推荐使用 Date.parse()new Date() 以获得更好的可读性。

1
const timestamp = +new Date(); // 等价于 Date.now()

2. 赋值运算符:值存储与复合赋值

2.1 基础赋值运算符 =

= 将右侧表达式的值赋给左侧变量。赋值表达式本身有返回值——即被赋予的值。

1
2
let a;
console.log(a = 5); // 5 —— 赋值表达式的返回值是 5

这一特性使得链式赋值成为可能:

1
2
3
let x, y, z;
x = y = z = 10; // 从右到左:z=10 返回 10,赋给 y,y=10 返回 10,赋给 x
console.log(x, y, z); // 10 10 10

2.2 复合赋值运算符

复合赋值运算符对变量执行运算后将结果赋回该变量。它们本质上是“先运算,再赋值”的语法糖。

运算符 等价于 运算符 等价于
+= x = x + y -= x = x - y
*= x = x * y /= x = x / y
%= x = x % y **= x = x ** y
<<= x = x << y >>= x = x >> y
>>>= x = x >>> y &= x = x & y
` =` `x = x y`
&&= x && (x = y) ||= x || (x = y)
??= x ?? (x = y)
1
2
3
4
5
6
7
8
9
10
11
12
13
let num = 10;
num += 5; // 等价于 num = num + 5 → 15
num *= 2; // 等价于 num = num * 2 → 30
console.log(num); // 30

// 逻辑复合赋值(短路求值)
let name = '';
name ||= 'Anonymous'; // name 为 falsy,赋值为 'Anonymous'
console.log(name); // 'Anonymous'

let count = 5;
count &&= 10; // count 为 truthy,赋值为 10
console.log(count); // 10

3. 比较运算符:== vs === 与字典序

比较运算符返回布尔值,是条件判断和控制流的基础。

3.1 严格相等 === 与宽松相等 ==

运算符 名称 行为
=== 严格相等 类型不同直接返回 false;类型相同且值相同返回 true
!== 严格不等 === 的取反。
== 宽松相等 类型不同时强制转换类型后再比较(规则复杂,见第 44 课 5.2)。
!= 宽松不等 == 的取反。

实践原则:永远使用 ===!==,除非你明确需要 null == undefined 这种特殊行为。

特殊比较陷阱

1
2
3
4
console.log(NaN === NaN);       // false —— NaN 是唯一不等于自身的值
console.log(0 === -0); // true
console.log(Object.is(NaN, NaN)); // true —— Object.is 是更精确的比较
console.log(Object.is(0, -0)); // false —— Object.is 能区分 0 和 -0

3.2 大小比较与字典序

关系运算符 <><=>= 的比较规则:

  • 两个字符串比较 → 字典序(逐字符 Unicode 码点)。
  • 其他情况 → 尝试将两个操作数转为数值比较。
  • 任一操作数转为 NaN → 结果始终为 false
1
2
3
4
console.log('apple' < 'banana'); // true —— 字典序:'a'(97) < 'b'(98)
console.log('10' > 2); // true —— '10' 转为 10 > 2
console.log('10' > '2'); // false —— 字典序:'1'(49) < '2'(50)
console.log('abc' > 1); // false —— Number('abc') → NaN,与 NaN 比较总是 false

4. 逻辑运算符:短路求值与返回值

逻辑运算符 &&||?? 不仅返回布尔值,还可以返回其中一个操作数的原始值。这种特性常用于默认值赋值条件执行

4.1 ||(逻辑或):返回第一个 Truthy 值

expr1 || expr2 的行为:

  • expr1 为 truthy,返回 expr1(短路,不计算 expr2)。
  • expr1 为 falsy,返回 expr2(无论 expr2 是 truthy 还是 falsy)。
1
2
3
console.log(0 || 'default');    // 'default' —— 0 是 falsy
console.log('Hello' || 42); // 'Hello' —— 第一个就是 truthy
console.log(null || undefined); // undefined —— 全 falsy,返回最后一个

经典用法:为变量提供默认值(但当 0'' 是有效值时会有问题,此时应用 ??)。

4.2 &&(逻辑与):返回第一个 Falsy 值

expr1 && expr2 的行为:

  • expr1 为 falsy,返回 expr1(短路)。
  • expr1 为 truthy,返回 expr2
1
2
3
console.log(0 && 'Hello');  // 0 —— 第一个就是 falsy
console.log(1 && 'Hello'); // 'Hello' —— 都 truthy,返回最后一个
console.log(1 && 0); // 0 —— 第二个是 falsy

经典用法:条件执行(如 isLoggedIn && showDashboard())。

4.3 ??(空值合并运算符,ES2020)

expr1 ?? expr2:仅当 expr1nullundefined 时返回 expr2,否则返回 expr1。这与 || 的关键区别在于它不视 0''false 为假值

1
2
3
4
console.log(0 ?? 'default');   // 0 —— 0 不是 null/undefined
console.log('' ?? 'default'); // '' —— 空字符串也是有效值
console.log(null ?? 42); // 42
console.log(undefined ?? 42); // 42

| 表达式 | || 结果 | ?? 结果 |
| ——————– | ———– | ———– |
| 0 || 'default' | 'default' | 0 |
| '' || 'default' | 'default' | '' |
| false || 'default' | 'default' | false |
| null ?? 'default' | 'default' | 'default' |

最佳实践:为变量提供默认值时优先使用 ??,除非你确定需要将所有 falsy 值都视为“无效”。


5. 位运算符:32 位整数操作

位运算符将操作数视为32 位有符号整数BigInt 除外),在其二进制表示上逐位操作。

运算符 名称 示例 说明
& 按位与 5 & 3 两位都为 1 则得 1。5(0101) & 3(0011) → 1(0001)。
| 按位或 5 | 3 任一位为 1 则得 1。5(0101) | 3(0011) → 7(0111)。
^ 按位异或 5 ^ 3 两位不同则得 1。5(0101) ^ 3(0011) → 6(0110)。
~ 按位非 ~5 翻转每一位。~5-6(补码表示)。
<< 左移 5 << 1 各二进制位左移 1 位,右侧补 0。5(0101) → 10(1010)。
>> 有符号右移 5 >> 1 各二进制位右移 1 位,左侧补符号位。5(0101) → 2(0010)。
>>> 无符号右移 -5 >>> 1 各二进制位右移 1 位,左侧补 0。结果总是正数。

应用场景:权限系统(用位掩码存储多个布尔状态)、颜色值操作、性能敏感的低级运算。

1
2
3
4
5
6
7
const READ = 1;    // 001
const WRITE = 2; // 010
const EXECUTE = 4; // 100

let permission = READ | WRITE; // 011 (3)
console.log(permission & READ); // 1(truthy)—— 拥有读权限
console.log(permission & EXECUTE); // 0(falsy)—— 没有执行权限

6. 其他重要运算符

6.1 三元条件运算符 ? :

JavaScript 唯一的三目运算符condition ? exprIfTrue : exprIfFalse

1
2
3
const age = 20;
const status = age >= 18 ? '成年人' : '未成年人';
console.log(status); // '成年人'

注意嵌套:三元运算符可以嵌套,但会严重损害可读性。超过一层嵌套时请改用 if-else

6.2 typeof 运算符

返回操作数的类型字符串。它是一个运算符,不是函数,因此括号是可选的(但常加括号以增强可读性)。

1
2
3
4
5
6
7
8
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)
console.log(typeof Symbol()); // 'symbol'
console.log(typeof 10n); // 'bigint'
console.log(typeof function(){}); // 'function'(函数是特殊的对象,但 typeof 返回 'function')

6.3 void 运算符

void 对任何表达式求值并**始终返回 undefined。其主要历史用途是在 <a href="javascript:void(0)"> 中阻止链接跳转,但现代代码应使用 <button>event.preventDefault()**。

1
2
console.log(void 0);           // undefined
console.log(void (10 + 20)); // undefined

6.4 逗号运算符 ,

逗号运算符从左到右求值每个表达式,并返回最后一个表达式的值。它主要用于在单行中执行多个操作(如 for 循环中声明多个变量)。

1
2
const x = (1, 2, 3); // x = 3
for (let i = 0, j = 10; i < j; i++, j--) { /* ... */ }

注意:不要将逗号运算符与函数参数、数组字面量中的逗号分隔符混淆。

6.5 可选链 ?. 与空值合并 ?? 的组合

第 3 课(超链接)和第 45 课已介绍过可选链。这里强调它与 ?? 的经典组合:

1
2
3
4
const user = null;
const userName = user?.profile?.name ?? '访客';
// user?.profile 短路返回 undefined,?? 将 undefined 替换为 '访客'
console.log(userName); // '访客'

7. 运算符优先级与结合性

当多个运算符出现在同一个表达式中时,优先级决定求值顺序,结合性决定同优先级运算符的求值方向。

7.1 快速参考:从高到低的优先级表(精选)

优先级 运算符类别 运算符示例 结合性
1 成员访问 / 计算属性 obj.propobj[expr]fn()
2 一元运算符 +-!~typeofvoiddelete
3 指数 **
4 乘除取余 */%
5 加减 +-
6 位移 <<>>>>>
7 比较 <><=>=instanceofin
8 相等 ==!====!==
9 按位与 &
10 按位异或 ^
11 按位或 |
12 逻辑与 &&
13 逻辑或 / 空值合并 ||??
14 条件(三元) ? :
15 赋值 =+=-=
16 逗号 ,

7.2 结合性实例

右结合:指数 **、一元运算符、赋值、三元运算符。

1
2
3
4
5
6
7
8
9
10
// 指数右结合
console.log(2 ** 3 ** 2); // 512 —— 先 3**2=9,再 2**9=512

// 赋值右结合
let a, b;
a = b = 5; // 先 b=5,再 a=5

// 三元右结合
const result = true ? 'yes' : false ? 'no' : 'maybe'; // 'yes'
// 解析为:true ? 'yes' : (false ? 'no' : 'maybe')

7.3 不确定时:加括号

括号 () 的优先级最高(实际上是分组运算符,不属于优先级表)。当对求值顺序有疑虑时,始终使用括号显式指定意图,不要依赖记忆优先级表。括号能消除歧义,提升代码可读性,避免因优先级理解错误导致的 Bug。

1
2
3
4
5
// 意图模糊
const result = a + b * c;

// 意图清晰(即使括号不影响结果,也明确表达了意图)
const result = a + (b * c);

课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) 5 + 3 + '2' 的结果是?
    A. 10
    B. '82'
    C. '532'
    D. '10'

  2. (单选) 0 ?? 'default'0 || 'default' 的结果分别是?
    A. 0'default'
    B. 'default''default'
    C. 00
    D. 'default'0

  3. (填空) 指数运算符 ** 的结合性是 ______(左/右)。

  4. (多选) 以下哪些运算符会进行短路求值(即第二个操作数可能不被计算)?
    A. &&
    B. ||
    C. ??
    D. +

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要编写一个 JavaScript 工具函数 calculate(expression),能够对一个简单的字符串表达式进行求值。表达式仅包含两个正整数和四种运算符(+-*/),格式为 "数字 运算符 数字"(如 "15 + 3")。要求如下:

  • 使用 const 声明函数,内部变量优先 const
  • 使用字符串方法(如 split)解析操作数和运算符。
  • 使用 switch 语句根据运算符执行对应计算。
  • 使用 Number() 将字符串操作数转为数值。
  • 处理除数为 0 的情况:若除数为 0,返回字符串 'Error: Division by zero'
  • 处理无效运算符:返回 'Error: Invalid operator'
  • 添加 JSDoc 注释说明参数、返回值和异常处理。
  • 提供至少 5 个测试用例的调用示例(含除零和无效运算符)。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 JavaScript 代码。提示词中需明确指定字符串解析方式、错误处理逻辑和代码风格。


课后练习答案

一、概念自测答案

  1. B

    • 解析:5 + 3 = 8(数值加法),然后 8 + '2' = '82'(字符串拼接)。
  2. A

    • 解析:0 ?? 'default'00 不是 null/undefined);0 || 'default''default'0 是 falsy)。
    • 解析:2 ** 3 ** 2 先算 3 ** 2 = 9,再算 2 ** 9 = 512
  3. A、B、C

    • 解析:&&||?? 都会在第一个操作数确定结果时短路,不计算第二个操作数。+ 总是计算两个操作数。

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请编写一个 JavaScript 函数 calculate(expression),接收一个字符串表达式,如 '15 + 3',计算并返回结果。要求:

  • 使用 const 声明函数,内部变量使用 const
  • 使用 expression.split(' ') 按空格拆分为 [operand1, operator, operand2]
  • 使用 Number() 将操作数字符串转为数值。
  • 使用 switch (operator) 匹配 '+''-''*''/' 四种情况。除法时检查除数 num2 === 0,若为 0 返回 'Error: Division by zero'
  • default 分支返回 'Error: Invalid operator'
  • 添加 JSDoc 注释,说明 @param {string} expression@returns {number|string} 以及错误情况。
  • 在注释中提供测试用例:calculate('10 + 5') // 15calculate('8 * 3') // 24calculate('10 / 0') // 'Error: Division by zero'calculate('5 ^ 2') // 'Error: Invalid operator'calculate('100 - 37') // 63
  • 代码简洁,遵循 Airbnb 风格。直接输出完整代码。”
CATALOG
  1. 1. 第46课:运算符与表达式
    1. 1.1. 1. 算术运算符:数值计算与 + 的特殊性
      1. 1.1.1. 1.1 六种基础算术运算符
      2. 1.1.2. 1.2 加法运算符 + 的字符串拼接行为
      3. 1.1.3. 1.3 一元 + 和 - 运算符
    2. 1.2. 2. 赋值运算符:值存储与复合赋值
      1. 1.2.1. 2.1 基础赋值运算符 =
      2. 1.2.2. 2.2 复合赋值运算符
    3. 1.3. 3. 比较运算符:== vs === 与字典序
      1. 1.3.1. 3.1 严格相等 === 与宽松相等 ==
      2. 1.3.2. 3.2 大小比较与字典序
    4. 1.4. 4. 逻辑运算符:短路求值与返回值
      1. 1.4.1. 4.1 ||(逻辑或):返回第一个 Truthy 值
      2. 1.4.2. 4.2 &&(逻辑与):返回第一个 Falsy 值
      3. 1.4.3. 4.3 ??(空值合并运算符,ES2020)
    5. 1.5. 5. 位运算符:32 位整数操作
    6. 1.6. 6. 其他重要运算符
      1. 1.6.1. 6.1 三元条件运算符 ? :
      2. 1.6.2. 6.2 typeof 运算符
      3. 1.6.3. 6.3 void 运算符
      4. 1.6.4. 6.4 逗号运算符 ,
      5. 1.6.5. 6.5 可选链 ?. 与空值合并 ?? 的组合
    7. 1.7. 7. 运算符优先级与结合性
      1. 1.7.1. 7.1 快速参考:从高到低的优先级表(精选)
      2. 1.7.2. 7.2 结合性实例
      3. 1.7.3. 7.3 不确定时:加括号
    8. 1.8. 课后练习
      1. 1.8.1. 一、概念自测(选择题 / 填空题)
      2. 1.8.2. 二、AI 编程任务:编写面向 AI 的提示词
    9. 1.9. 课后练习答案
      1. 1.9.1. 一、概念自测答案
      2. 1.9.2. 二、AI 编程任务参考答案(提示词示例)