运算符让表达式发挥作用
每一行稍微复杂的 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,就用 ??。如果你确实希望任何假值都触发后备值,就继续用 ||。