Menu

JavaScript 运算符:算术、比较、逻辑等运算符详解

JavaScript 提供的各种运算符:做数学运算、比较值、组合布尔值和赋值,以及实践中真正会踩坑的怪现象。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

运算符让表达式发挥作用

每一行稍微复杂的 JavaScript 代码都由运算符构成:运算符是接收一个或两个值并产生新值的符号。+ 做加法,=== 做比较,&& 组合布尔值,? : 在两个值中二选一。它们大多和其他语言中的一样;少数几个有 JavaScript 特有的怪现象,值得提前了解。

我们会按类别逐一介绍,最后讲那些用得最少、但需要时很重要的运算符。

算术运算符

基本运算符的行为和你预想的一样:

有几点值得注意:

  • / 总是做浮点除法。7 / 2 是 3.5,不是 3。想得到整数,就用 Math.floor(7 / 2) 或 Math.trunc(7 / 2)。
  • % 是求余,不是数学意义上真正的取模。结果的符号和左操作数相同:-7 % 3 是 -1,不是 2。
  • + 是重载的。只要有一边是字符串,它就做拼接而不是加法:"3" + 1 是 "31"。稍后会详细讲。

自增和自减

count++ 和 ++count 的区别,只有在同一行中使用表达式的值时才有影响。作为单独的语句,它们效果相同。大多数风格指南为了清晰起见,更推荐 count += 1。

+ 运算符有两副面孔

每个人都至少会在这里栽一次跟头:

只要有一个操作数是字符串,+ 就会变成字符串拼接,并把另一边转换成字符串。其他所有算术运算符的方向正好相反:它们会把字符串转换成数字:

要点是:拼接字符串时,优先使用模板字符串(`price: ${5}`)。做数学运算时,要确保输入的确是数字:用 Number(x) 或 parseInt(x, 10) 显式转换。

比较运算符

比较运算返回布尔值。分为两种:严格比较和宽松比较。

=== 和 !== 是严格比较:值相同且类型相同。== 和 != 会在比较前转换类型,这会带来意外,比如 null == undefined 为 true,[] == false 也为 true。默认使用 === 和 !==。唯一常见的例外是 x == null,它是“x 是 null 或 undefined 吗?”的便捷写法。

大小比较运算符对数字的行为和你预想的一样,对字符串则按字母顺序比较:

字符串比较基于字符编码,所以区分大小写。如果需要符合人类习惯的排序,就用 String.prototype.localeCompare。

逻辑运算符

&&(与)、||(或)和 !(非)用来组合布尔值,但它们比纯粹的布尔代数要有趣得多。

关键在于:&& 和 || 返回的不是 true 或 false,而是它们的某个操作数。&& 返回第一个假值,如果全都是真值,就返回最后一个。|| 返回第一个真值,如果全都是假值,就返回最后一个。

这就是为什么你会看到 const displayName = user.name || "Guest" 这样的写法:选出第一个非空的值。它很简洁,但要小心:|| 会把 0、"" 和 false 都当作需要后备值的情况。如果这些对你来说是合法的值,就改用 ??(见下文)。

这两个运算符还会短路:如果左侧已经决定了结果,右侧就根本不会执行。

空值合并运算符 ??

?? 和 || 类似,但只在 null 或 undefined 时触发,0、"" 或 false 都不会触发。

当合法的值可能是假值时,比如计数、空字符串、明确的 false,就用 ??。当任何假值都应该表示“缺失”时,就用 ||。在现代代码中,对于有非空默认值的可选值,?? 是更安全的默认选择。

赋值运算符

= 用来赋值。复合形式把赋值和另一个运算符结合起来:

还有逻辑赋值运算符:||=、&&= 和 ??=,只在当前值满足条件时才赋值:

不用写冗长的 if 语句就能补全默认值,这些运算符很方便。

三元运算符

condition ? a : b 是 if/else 的表达式版本。条件为真值时返回 a,否则返回 b:

三元运算符最适合简短的取值选择。一旦嵌套,就会变得难以阅读:如果你发现自己在写 a ? b : c ? d : e,就改用 if/else 或查找对象。

typeof 与 instanceof

typeof 返回一个描述操作数类型的字符串:

有两个陷阱值得记住:typeof null 是 "object"(1995 年留下的 bug,如今已成永久),数组也会返回 "object"。判断数组用 Array.isArray(x),判断 null 用 x === null。

instanceof 检查一个对象是否由某个特定的构造函数创建:

展开和剩余使用同一个 ...

你会在两种地方看到 ...。作为展开语法,它把可迭代对象拆成单独的元素:

作为剩余语法,它把多个值收集到一个数组中,通常用在函数参数或解构中:

语法相同,作用相反。展开是拆开,剩余是收集。具体是哪一种由上下文决定:在调用或字面量中是展开;在参数列表或解构模式中是收集。

运算符优先级(拿不准就加括号)

运算符有优先级,决定混用时先执行哪一个。乘法优先于加法,比较优先于逻辑运算,依此类推:

完整的优先级表很长,没有人能全部记住。一个简单的习惯就能应对 99% 的情况:混用运算符时,只要你不能确定顺序,就加上括号。代码读起来更清楚,也不依赖读者的记忆力。

位运算符(通常不是你想要的)

为了完整起见:&、|、^、~、<<、>> 和 >>> 作用于整数的二进制表示。你会在图形代码、底层协议,以及偶尔用位掩码表示标志的 API 中见到它们。

一个常见但值得商榷的技巧:n | 0 会把数字截断成 32 位整数,过去人们常把它当作更快的 Math.trunc 来滥用。不要这样做:Math.trunc 更清晰,而且对超出 32 位范围的数字同样有效。

下一章:if/else

运算符产生值;if/else 用这些值来选择要运行的代码分支。上面这些比较和逻辑运算符,大多数时候都是把结果交给条件语句使用,而这正是下一页的内容。

常见问题

JavaScript 有哪些主要的运算符?

JavaScript 有算术运算符(+、-、*、/、%、**)、比较运算符(===、!==、<、>)、逻辑运算符(&&、||、!)、赋值运算符(=、+=、-=),以及几个特殊的运算符,如三元运算符 ? :、typeof 和空值合并运算符 ??。它们是构建表达式和控制流程的基本单元。

JavaScript 中 == 和 === 有什么区别?

=== 检查值和类型是否都相同。== 会在比较前转换类型,所以 0 == "0" 是 true,而 0 === "0" 是 false。默认使用 ===:== 的类型转换规则很微妙,足以造成代码审查时发现不了的 bug。

JavaScript 中的三元运算符有什么作用?

condition ? a : b 是一个返回值的单行 if/else。如果 condition 为真值,结果是 a,否则是 b。它适合写简短的条件表达式,比如 const label = count === 1 ? 'item' : 'items',但三元运算符一旦嵌套,很快就会变得难以阅读。

什么时候应该用 ?? 而不是 ||?

|| 遇到任何假值都会使用后备值,包括 0、"" 和 false。?? 只在 null 或 undefined 时使用后备值。如果你希望 count ?? 10 保留合法的 0,就用 ??。如果你确实希望任何假值都触发后备值,就继续用 ||。

Coddy 编程语言插图

和 Coddy 一起学编程

开始