运算符与表达式

让程序学会计算和判断

🎯 学习目标

学完本章,你将能够:

  • 能够区分算术、比较、逻辑、赋值四类运算符,并正确使用它们构建表达式
  • 能够解释 === 与 == 的区别,始终使用严格相等进行比较
  • 能够利用短路求值(&&、||、??)编写简洁的条件赋值代码
  • 能够使用三元运算符替代简单的 if-else 分支
  • 能够查阅运算符优先级表,正确分析复杂表达式的执行顺序

📌 有编程基础?算术、比较、逻辑运算符各语言通用。本章对 CS 学生真正有价值的是两处:JS 的隐式类型转换([]" + 1 之类怪异结果的根源)和空值合并 ??(C/Java 里没有,ES2020 才加入)。其余可略读,函数与作用域才是 JS 的硬骨头。

你有没有想过

电商网站上,购物车能自动算出总价,还能判断是否满足包邮条件——这是怎么做到的?

答案是运算符。+ 算价格,> 判断是否满减,&& 组合多个条件。

运算符是程序的基本功。就像学数学先学加减乘除一样,学编程先学运算符。本章带你掌握 JS 中的所有常用运算符,以及那些容易踩坑的隐式类型转换。

表达式与运算符

代码的最小计算单元

表达式(Expression)是能够产生一个值的代码片段。运算符(Operator)是连接操作数、执行运算的符号。

说白了,表达式就是一个算式。运算符就是算式里的加减乘除号。每个表达式都会算出一个结果。

// 这些都是表达式,每个都能算出一个值
10 + 3;           // 13
"Hello" + "World"; // "HelloWorld"
5 > 3;            // true
typeof 42;        // "number"

// 表达式可以嵌套
let result = (10 + 5) * 2; // 30

Part 1: 算术运算符

加减乘除,编程里也一样,只不过多了几个好用的工具

表达式和运算符的概念搞清楚了,先从最熟悉的开始。加减乘除你肯定认识,但 % 取模能干什么?** 怎么算幂?a++ 和 ++a 到底有什么区别?这一节把这些全讲透。掌握之后,购物车算总价、循环里做计数,都不在话下。

基础算术

你认识 + - * /,还有三个新朋友

console.log(10 + 3);   // 13  加法
console.log(10 - 3);   // 7   减法
console.log(10 * 3);   // 30  乘法
console.log(10 / 3);   // 3.333... 除法(返回浮点数)
console.log(10 % 3);   // 1   取模(余数)
console.log(10 ** 3);  // 1000 幂运算(ES7)

% 取模

求余数。常用场景:判断奇偶、循环取值。

** 幂运算

相当于 Math.pow()。2**10 就是 2 的 10 次方。

自增与自减

++ 和 --,位置不同结果不同

let a = 5;
console.log(a++);  // 5(先返回当前值,再加 1)
console.log(a);    // 6(这时 a 已经变成 6 了)

let b = 5;
console.log(++b);  // 6(先加 1,再返回新值)
console.log(b);    // 6

// 实际开发中,推荐写法更清晰:
// a = a + 1; 或 a += 1;
症状:console.log(a++) 打印出来的值比预期少 1。
原因:a++ 是后置自增,先返回旧值再加 1。
修复:如果需要立即使用新值,用前置 ++a,或者单独写一行 a += 1。

赋值运算符

+=、-= 这些简写你肯定见过

let x = 10;
x += 5;   // x = x + 5 → 15
x -= 3;   // x = x - 3 → 12
x *= 2;   // x = x * 2 → 24
x /= 4;   // x = x / 4 → 6
x %= 4;   // x = x % 4 → 2
x **= 3;  // x = x ** 3 → 8

ES2021 还新增了三个逻辑赋值运算符:

let y = null;
y ??= "默认值"; // y 为 null,赋值为 "默认值"

let z = 0;
z ||= 42;      // z 为 falsy (0),赋值为 42

let w = "hello";
w &&= "world"; // w 为 truthy,赋值为 "world"

Part 2: 比较运算符

比较两个值的大小或相等性,结果是布尔值 true 或 false

算术运算搞定了价格计算,但电商还要判断"满不满足包邮条件"——这就需要比较。

JS 的比较运算符看似简单,=== 和 == 却是无数 bug 的源头。

为什么 5 == "5" 是 true,而 5 === "5" 是 false?

搞清楚这个,你就避开了 JS 最大的坑之一。

比较运算符

大于小于,一看就懂

console.log(10 > 5);     // true
console.log(10 >= 10);   // true
console.log(10 < 5);     // false
console.log(10 <= 10);   // true

// 字符串也能比较(按 Unicode 码点)
console.log("a" < "b");     // true
console.log("abc" < "abd"); // true
console.log("A" < "a");     // true(大写字母码点更小)

注意:字符串比较是逐字符比较的。就像查字典,先看第一个字母,第一个字母相同再看第二个。

这个比喻只是为了帮你建立直观感受。在计算机内部,字符串比较实际上是逐个字符比较 Unicode 码点数值的大小。

=== vs ==

JavaScript 最重要的知识点之一

===(严格相等)要求类型和值都相等。==(宽松相等)会先做类型转换再比较。

// == 会做隐式转换(不推荐!)
console.log(5 == "5");      // true(字符串"5"被转成数字5)
console.log(0 == false);    // true
console.log("" == false);   // true
console.log(null == undefined); // true

// === 不做转换(推荐!)
console.log(5 === "5");     // false(类型不同,直接判不同)
console.log(0 === false);   // false
console.log("" === false);  // false
铁律:永远使用 === 和 !==,不要使用 == 和 !=。唯一例外是 value == null 可以同时匹配 null 和 undefined。

== 转换规则

知道规则是为了避免踩坑,不是为了用它

操作数类型转换规则
数字 vs 字符串字符串转数字
布尔 vs 其他布尔转数字(true→1, false→0)
对象 vs 原始类型对象调用 valueOf()/toString()
null vs undefined相等,且不与其他值相等
// NaN 是个奇葩
console.log(NaN === NaN);       // false(自己不等于自己!)
console.log(Number.isNaN(NaN)); // true(正确的判断方式)

// Object.is —— 比 === 更严格
console.log(Object.is(0, -0));     // false(=== 返回 true)
console.log(Object.is(NaN, NaN));  // true

试一试

练习一

任务:预测以下每一行的结果,然后运行验证。

console.log(1 == "1");
console.log(1 === "1");
console.log(null == undefined);
console.log(null === undefined);
console.log(NaN == NaN);

参考答案

console.log(1 == "1");          // true
console.log(1 === "1");         // false
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(NaN == NaN);        // false

Part 3: 逻辑运算符

用于组合条件判断,程序做决策全靠它们

单个条件会判了,但现实往往是"会员 且 消费满 200 才打折"这种组合条件。&&、||、! 就是用来拼接多个条件的。它们还有一个"短路"的超能力——提前看到答案就不再往后算了。学会这一节,你就掌握了程序做决策的核心逻辑。

逻辑运算符

&&(与)、||(或)、!(非)

// && 与——两个都为 true 才是 true
console.log(true && true);   // true
console.log(true && false);  // false

// || 或——有一个为 true 就是 true
console.log(true || false);  // true
console.log(false || false); // false

// ! 非——取反
console.log(!true);   // false
console.log(!false);  // true
console.log(!0);      // true
console.log(!!"hello"); // true(双重否定转为布尔值)

逻辑运算符就像门卫。&& 是"两个证件都查",|| 是"有一个就行",! 是"反过来"。

这个比喻只是为了帮你建立直观感受。在计算机内部,逻辑运算符是对操作数进行布尔转换后,按真值表进行逻辑运算的。

短路求值

结果一旦确定,后面的就不看了

短路求值(Short-circuit)是指逻辑运算符在能够确定结果时,立即停止计算,不再执行后面的表达式。

说白了,就是看到答案了就不再算了。

// && 短路:第一个为 falsy,直接返回第一个
console.log(0 && "hello");   // 0
console.log(null && someFunc()); // null(someFunc 不会被调用!)

// || 短路:第一个为 truthy,直接返回第一个
console.log("hello" || "world"); // "hello"
console.log(1 || someFunc());    // 1(someFunc 不会被调用!)

?? 空值合并

比 || 更精准的默认值写法

??(空值合并运算符)只有当左侧为 null 或 undefined 时才返回右侧的值。

// || 的问题:0 和 "" 也被视为 falsy
let count = 0 || 10;       // 10(0 被当作"没有值"了!)
let name = "" || "匿名";    // "匿名"(空字符串也被当"没有值"了!)

// ?? 只对 null 和 undefined 生效
let count2 = 0 ?? 10;      // 0(0 被保留了!)
let name2 = "" ?? "匿名";   // ""(空字符串被保留了!)
表达式结果说明
0 || 1010|| 把 0 视为 falsy
0 ?? 100?? 保留 0
null || "默认""默认"两者行为相同
null ?? "默认""默认"两者行为相同

Part 4: 其他运算符

一元运算符、三元运算符——小而美的工具

算术、比较、逻辑三大类学完了,还有几个零散但好用的运算符。typeof 能快速检查类型,+ 一元加能秒转数字,三元运算符 ? : 一行搞定 if...else。它们个头不大,但在日常代码里出场率极高。这一节认识它们,后面写代码会简洁很多。

一元运算符

只需要一个操作数就能工作

// typeof —— 判断类型
console.log(typeof 42);        // "number"
console.log(typeof "hello");   // "string"
console.log(typeof null);      // "object"(历史 bug,见下方说明)

// 一元加 —— 强制转数字
console.log(+"42");    // 42
console.log(+true);    // 1
console.log(+false);   // 0
console.log(+null);    // 0
console.log(+"hello"); // NaN

// 一元减
console.log(-"42");    // -42

// delete —— 删除对象属性
let obj = { a: 1, b: 2 };
delete obj.b;
console.log(obj); // { a: 1 }

typeof null 返回 "object" 是 JS 的历史遗留 bug。初版 JS 中,值用 32 位存储,前 3 位标记类型,000 代表对象——而 null 的所有位都是 0,刚好也被识别成了 "object"。这个行为无法修复(会破坏现有代码),所以判断 null 要用 value === null,而不是 typeof。

三元运算符

一行代码搞定 if...else

三元运算符(也叫条件运算符)是 JavaScript 中唯一需要三个操作数的运算符。语法:条件 ? 值1 : 值2。

let age = 20;
let status = age >= 18 ? "成年" : "未成年";
console.log(status); // "成年"

// 实际应用:函数中的条件返回
function greet(name) {
    return name ? `Hello, ${name}!` : "Hello, stranger!";
}

// 可以嵌套(但别超过一层,否则可读性很差)
let score = 85;
let grade = score >= 90 ? "A" :
            score >= 80 ? "B" :
            score >= 70 ? "C" : "D";
console.log(grade); // "B"

Part 5: 优先级与陷阱

运算符有优先级,类型有隐式转换,搞清这两个 bug 少一半

所有运算符都认识了,最后一个问题是:它们凑在一起时,谁先算?1 + 2 + "3" 为什么是 "33"?null >= 0 为什么是 true?这些"反直觉"的结果背后,是运算符优先级和隐式类型转换在作怪。搞懂规则不是为了背下来,而是遇到诡异结果时知道从哪里排查。

运算符优先级

谁先算?括号说了算

优先级运算符说明
最高()分组——括号优先
高++ -- ! typeof一元运算符
**幂运算
* / %乘除取模
+ -加减
< <= > >=比较
== != === !==相等
&&逻辑与
||逻辑或
??空值合并
?:三元条件
最低= += -=等赋值

简单记忆:和数学一样,先乘除后加减。拿不准的时候,加括号就对了。

隐式转换陷阱

这些表达式的结果可能出乎意料

// + 遇到字符串就变成拼接
console.log(1 + 2);       // 3(数字加法)
console.log("1" + 2);     // "12"(字符串拼接!)
console.log(1 + 2 + "3"); // "33"((1+2)=3,3+"3"="33")
console.log("1" + 2 + 3); // "123"("1"+2="12","12"+3="123")

// 比较运算符的隐式转换
console.log("10" > 9);    // true("10" 转数字 10)
console.log("10" > "9");  // false(字符串逐字符比较)

// null 的诡异行为
console.log(null > 0);    // false
console.log(null == 0);   // false(null 只与 undefined 宽松相等)
console.log(null >= 0);   // true(比较运算符将 null 转为 0)
避坑指南:1) 永远用 === 而非 ==;2) 注意 + 遇到字符串时的拼接行为;3) 拿不准时用 Number()、String()、Boolean() 显式转换。

显式转换实战

用明确的转换替代隐式猜测

上一章介绍了类型转换的基础。这里聚焦实际开发中最常用的转换模式——尤其是处理用户输入时。

// 字符串转数字——三种方式的区别
Number("42px");    // NaN——遇到非数字字符就失败
parseInt("42px");  // 42——提取开头的数字部分
parseFloat("3.14abc"); // 3.14

// 处理用户输入(永远是字符串!)
let userInput = " 42 ";
console.log(+userInput);           // 42(一元+会自动去掉空格)
console.log(parseInt(userInput));  // 42

// 任意值转布尔——双重否定技巧
console.log(!!"hello"); // true
console.log(!!0);       // false
console.log(!!null);    // false

// 安全的默认值处理
let config = { timeout: null };
let timeout = Number(config.timeout) || 3000; // null→0→falsy→用3000

显式转换不是"多余的一步",而是给其他开发者(包括未来的你)一个明确的信号:我知道这里会发生类型变化,我是故意的。

常见错误诊断

调试技巧:遇到不符合预期的结果时,先用 typeof 检查操作数的类型。大多数运算符问题都是类型引起的。

问题一:两个数字相加变成了字符串拼接

症状:1 + 2 结果是 "12" 而不是 3。

原因:其中一个操作数是字符串。+ 遇到字符串会变成拼接运算。

修复:用 Number() 或 +(一元加)显式转为数字。

let a = "5";
let b = 3;
console.log(a + b);      // "53"(字符串拼接)
console.log(+a + b);     // 8(先转数字再加)

问题二:空数组被当作 true

症状:[] 在条件判断中被当作 true,但直觉上"空的"应该是 false。

原因:在 JS 中,所有对象(包括空数组、空对象)都是 truthy。

修复:用 arr.length === 0 判断数组是否为空。

综合练习

练习二:价格计算器

任务:用变量存储商品的单价和数量,计算并输出包含折扣和小计的信息。

$ node price.js
单价: ¥99.9
数量: 3
小计: ¥299.7
八折后: ¥239.76
满200减30: ¥209.76

提示:用分为单位避免浮点问题,用 toFixed() 格式化,用三元运算符判断是否满减。

参考答案

const price = 9990; // 以分为单位
const qty = 3;
const subtotal = price * qty;
const discounted = subtotal * 0.8;
const finalPrice = discounted >= 20000
    ? discounted - 3000
    : discounted;

console.log(`单价: ¥${(price / 100).toFixed(1)}`);
console.log(`数量: ${qty}`);
console.log(`小计: ¥${(subtotal / 100).toFixed(1)}`);
console.log(`八折后: ¥${(discounted / 100).toFixed(2)}`);
console.log(`满200减30: ¥${(finalPrice / 100).toFixed(2)}`);

练习三:表达式拆解

任务:以下代码输出什么?先写出答案,再运行验证。

let a = 1, b = "2", c = 3;
console.log(a + b + c);
console.log(a + c + b);
console.log(a + +b + c);
console.log(typeof (a + b));

参考答案

// "123" — a+b 拼接为"12",再加 c 仍为拼接
// "42"  — a+c=4(数字),4+"2"="42"
// 6     — +b 将"2"转为数字2,1+2+3=6
// "string" — a+b 的结果是字符串

📝 要点总结

本章核心知识点

运算符分类

算术、比较、逻辑、赋值四大类

严格相等

始终用 === 和 !==,避免隐式转换

短路求值

&&、||、?? 的求值规则和实际应用

三元运算符

简洁的条件表达式替代 if-else

优先级

不确定时加括号,代码更清晰

下一章

现在你已经能让程序进行计算和判断了。下一章我们学习控制流——让程序能根据条件走不同的路,能重复执行任务。

1 / X