JS 中 == 和 === 的区别
七崽爱吃小饼干2026/10/11阅读 0
先看一组输出,你大概会怀疑自己写了假的 JS:
js
0 == '' // true
0 == false // true
'' == false // true
null == 0 // false —— 但 Number(null) 明明是 0
null == false // false
null >= 0 // true —— 同一个 null,换个运算符结果就反了
[] == false // true
[] == ![] // true —— 自己等于自己的取反?
这些都不是玄学,全部是 == 的强制类型转换规则推导出来的结果。而 === 没这套规则,它只做一件事:类型不同直接为 false。
这篇文章把 ==(抽象相等)和 ===(严格相等)拆开讲清楚,最后给一张速查表和选型建议。
一、最核心的一句话区别
===:类型不同直接返回false,类型相同才比值。不转换。==:类型不同时先按规则转成同一类型,再比较。这个"转"就是一切坑的来源。
js
1 === '1' // false,Number vs String,类型不同
1 == '1' // true,字符串先转成数字再比
null === undefined // false,类型不同
null == undefined // true,这是规则里唯一的特例
二、=== 严格相等的三条规则
=== 规则很短,值不值得记都行,因为它符合直觉:
- 类型不同,返回
false。 - 类型相同且都是原始值,值相等返回
true;只有NaN === NaN是false。 - 类型相同且都是引用类型,比较引用地址,不是内容。
js
NaN === NaN // false,NaN 不等于任何值,包括自己
+0 === -0 // true,=== 不区分正负零
{} === {} // false,两个不同的对象地址
const o = {}
o === o // true,同一个引用
如果想连正负零和 NaN 都区分开,用 Object.is:
js
Object.is(NaN, NaN) // true
Object.is(-0, 0) // false
三、== 的转换规则
== 的比较逻辑可以压缩成五条,掌握这五条就能手推上面所有"奇怪"输出:
- 类型相同:退化成
===来比。 null和undefined:只有它们俩互相比较时为true,跟其他任何值都是false。- 数字和字符串:字符串转成数字再比。
- 布尔值参与比较:布尔值先转成数字(
true → 1,false → 0)。 - 对象和原始值比较:对象先通过
ToPrimitive转成原始值,再按上面规则比。
3.1 数字与字符串
js
123 == '123' // true,'123' → 123
123 == ' 123' // true,转换时会修剪首尾空白
123 == '' // false,'' → 0,123 ≠ 0
0 == '' // true,'' → 0
1 == '1x' // false,'1x' → NaN,NaN 不等于任何值
3.2 布尔值先转数字
布尔值在比较里永远是先变成 0 或 1,这导致很多反直觉结果:
js
1 == true // true,true → 1
0 == false // true,false → 0
2 == true // false,true → 1,2 ≠ 1
'0' == false // true,false → 0,'0' → 0
'' == false // true,false → 0,'' → 0
注意 '0' == false 为 true:字符串 '0' 在转数字时得到 0,不是"字符串非空就是真"。== 里的真假判断和 if 里的真假判断是两套东西。
3.3 null 和 undefined 的特殊规则
这是最容易被误判的一块。null 在 == 里不会被转成数字:
js
null == undefined // true,唯一的互相相等
null == 0 // false,null 不转数字
null == false // false
undefined == 0 // false
undefined == false // false
undefined == '' // false
所以想同时判断"是 null 或是 undefined",写 x == null 是安全且常见的写法,它能同时命中两者且不会误伤 0 和 ''。
但要小心:一旦换成大小比较,null 又会被转成数字,结果就翻转了:
js
null >= 0 // true,关系运算符走了 ToNumber,null → 0
null == 0 // false
四、对象参与比较:ToPrimitive 是关键
对象和原始值比较时,对象会先被转成原始值。默认顺序是先调 valueOf,拿不到原始值再调 toString:
js
[] == '' // true,[].toString() → ''
[] == 0 // true,[] → '' → 0
[1] == 1 // true,[1] → '1' → 1
[1, 2] == '1,2' // true,[1,2].toString() → '1,2'
这就解释了经典的一题:
js
[] == false // true
// 右侧 false → 0
// 左侧 [] → '' → 0
// 0 == 0 → true
[] == ![] // true
// ![] 先算,[] 是对象,转布尔为 true,取反得 false
// 于是又变成 [] == false,同上
对象的 toString 是可以被改写的,所以结果取决于你自己的实现:
js
const box = {
valueOf() { return 42 },
toString() { return 'box' },
}
box == 42 // true,valueOf 先返回 42
box == '42' // true
五、速查表
| 表达式 | 结果 | 原因 |
|---|---|---|
1 === '1' | false | 类型不同,=== 不转换 |
1 == '1' | true | 字符串转数字 |
null == undefined | true | 唯一特例 |
null == 0 | false | null 不参与数字转换 |
NaN == NaN | false | NaN 不等于任何值 |
Object.is(NaN, NaN) | true | 专门处理 NaN 与正负零 |
0 == '' | true | '' → 0 |
'0' == false | true | 布尔先转数字,字符串再转数字 |
[] == false | true | 都转成 0 |
[] == ![] | true | ![] 为 false,退化成上一行 |
{} == {} | false | 比较引用地址 |
六、怎么选:默认只用 ===
- 默认全部用
===,除非你有明确理由做类型转换。它可预测,不会因为隐式转换埋雷。 - 唯一建议用
==的场景:同时判断null和undefined,写x == null最简洁。 - 要区分
NaN或正负零,用Object.is。 - 如果确实需要比较不同类型的值,显式转换再比,别依赖
==的隐式规则。
js
// 不推荐:依赖隐式转换,读代码的人要停下来推
if (inputValue == 0) { /* ... */ }
// 推荐:意图明确
if (Number(inputValue) === 0) { /* ... */ }
ESLint 的 eqeqeq 规则默认要求全部使用 ===,只在对比 null 时放行 ==,正好和上面的选型一致。
小结
===不转换类型,类型不同直接false;==会先做强制转换再比。==的坑集中在布尔转换、字符串转数字、对象ToPrimitive三处。null只和undefined宽松相等,别的都false;但关系运算符又会把null当0。NaN和任何值都不相等,包括自己,要处理就用Object.is。- 选型一句话:能
===就===,只有判断null/undefined时用==。