运算符与表达式
让程序学会计算和判断
🎯 学习目标
学完本章,你将能够:
- 能够区分算术、比较、逻辑、赋值四类运算符,并正确使用它们构建表达式
- 能够解释 === 与 == 的区别,始终使用严格相等进行比较
- 能够利用短路求值(&&、||、??)编写简洁的条件赋值代码
- 能够使用三元运算符替代简单的 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 || 10 | 10 | || 把 0 视为 falsy |
0 ?? 10 | 0 | ?? 保留 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)
=== 而非 ==;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
优先级
不确定时加括号,代码更清晰
下一章
现在你已经能让程序进行计算和判断了。下一章我们学习控制流——让程序能根据条件走不同的路,能重复执行任务。