判断“是否相等”的两种方式
JavaScript 提供了两个相等运算符:===(严格)和 ==(宽松)。它们看起来几乎一样,行为却大不相同。
=== 检查两个操作数是否类型相同并且值相同。== 会先把操作数转换为同一种类型,然后再比较。这种转换,也就是类型强制转换,正是 JavaScript 相等比较“名声在外”的主要原因。
简短的结论是:默认使用 ===。长一点的解释值得读一遍,这样你就知道自己放弃了什么。
严格相等:你多半想要的那个
只有当两边的类型和值都一致时,=== 才返回 true。没有强制转换,也没有意外:
如果类型不一致,结果立刻就是 false。如果类型一致,JavaScript 再比较值。对原始值来说,比较的是值;对对象来说,比较的是引用(稍后会详细讲)。
!== 是严格不相等运算符,规则相同,结果相反。
宽松相等:伪装起来的类型转换
== 允许两边的类型不同。比较之前,它会经过一系列强制转换,试图让两边的类型一致:
确切的规则写在规范里,其实也没那么糟,但调试到一半时要想起它们就是另一回事了。"0" == false 为 true 这一点,连有经验的开发者都会中招。[] == false 也一样(结果同样是 true,因为数组会被转换为 "",而 "" 又被转换为 0)。
这就是为什么大多数代码风格指南,以及 ESLint 的 eqeqeq 规则,都要求默认使用 ===。多打一个字符,换来的是你真正记得住的规则。
唯一实用的 == 写法
有一个宽松相等的惯用法值得了解:当 x 为 null 或 undefined 时,x == null 返回 true,其他情况都返回 false。
对应的严格写法是 x === null || x === undefined,也能用,但读起来更啰嗦。很多代码库把 == null 当作唯一被认可的例外。选定一条规则,然后始终如一地执行。
对象按引用比较
对于对象、数组和函数,=== 和 == 问的是同一个问题:“两边是否指向内存中的同一个对象?”而不是“它们的内容是否相同?”
两个内容完全相同的对象字面量,仍然是两个不同的对象。每个人都至少会在这里栽一次。
要按值比较,你可以自己写一个辅助函数,使用库(lodash.isequal),或者对简单的普通对象用 JSON.stringify 序列化后比较:
JSON.stringify 只适用于简单数据:它会忽略函数、undefined 和 symbol,而且对于某些结构,它不能保证不同引擎中键的顺序一致。它只是一种快速检查,而不是通用的解决方案。
NaN 不等于任何值
当数值运算没有有意义的结果时,JavaScript 会返回 NaN(“不是一个数字”),比如 0/0、Number("abc")、Math.sqrt(-1)。只要任意一边是 NaN,两个相等运算符都返回 false,两边都是 NaN 时也一样:
要检测 NaN,使用 Number.isNaN(value)。不要使用较老的全局函数 isNaN:它会先对参数进行强制转换,所以 isNaN("hello") 会返回 true,这几乎从来都不是你想要的。
Object.is:几乎就是 ===,修正了两处
Object.is(a, b) 的行为与 === 相同,只有两种边界情况例外:
大多数时候你想要的是 ===。只有当你确实需要让 NaN 等于它自己,或者区分 +0 和 -0 时,才用 Object.is。这两种情况都很少见,但在数值计算代码和框架内部偶尔很重要(React 就用 Object.is 来比较状态)。
不相等运算符同样分为两种
!== 是严格的,!= 是宽松的,建议也一样:
默认使用 !==。如果你已经允许了 == null,也可以允许与之对应的 != null,用来检查“既不是 null 也不是 undefined”。
比较值的检查清单
需要比较两个值时,按下面的顺序过一遍:
- 原始值,预期类型相同? 用
===。 - 检查 null 或 undefined? 如果你的风格指南允许,
x == null没问题;否则用x === null || x === undefined。 - 检查
NaN? 用Number.isNaN(x)。 - 按引用比较对象?
===正好满足需求。 - 按内容比较对象? 写一个辅助函数、使用库,或者序列化。内置运算符帮不上忙。
坚持使用 ===,只在 == null 这种场景下把 == 当作特殊工具,你就能避开那些塞满 JavaScript 常见问题列表的相等比较怪癖。
下一步:运算符
相等运算只是 JavaScript 运算符中的一部分。下一篇文档会讲解其余的运算符:算术运算符、逻辑运算符、赋值运算符,以及日常代码中经常用到的简写运算符。
常见问题
JavaScript 中 == 和 === 有什么区别?
=== 是严格相等:只有当两个操作数类型相同、值也相同时才返回 true。== 是宽松相等:比较之前会先把操作数强制转换为相同的类型。1 === '1' 是 false,但 1 == '1' 是 true,因为 == 会先把字符串转换成数字。
JavaScript 中应该总是使用 === 吗?
默认情况下,是的。=== 的行为可预测,规则也很容易记住。唯一常见的例外是 x == null,它可以很方便地同时匹配 null 和 undefined。大多数 linter(ESLint 的 eqeqeq 规则)都会强制使用 ===,并允许按需开启这一种写法。
为什么 NaN === NaN 返回 false?
IEEE 754 规范规定,NaN 不等于任何值,包括它自己。所以只要涉及 NaN,所有相等运算符都返回 false。用 Number.isNaN(x) 检查一个值是否为 NaN,或者用 Object.is(NaN, NaN),它会返回 true。
JavaScript 中如何比较两个对象是否相等?
== 和 === 都是按引用比较对象,而不是按内容。{a: 1} === {a: 1} 是 false,因为它们是两个不同的对象。要按值比较,你需要自己写检查逻辑,使用 Lodash 的 isEqual 这样的库,或者对简单的普通对象用 JSON.stringify 序列化后比较。