创见博客
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

三、== 的转换规则

== 的比较逻辑可以压缩成五条,掌握这五条就能手推上面所有"奇怪"输出:

  1. 类型相同:退化成 === 来比。
  2. null 和 undefined:只有它们俩互相比较时为 true,跟其他任何值都是 false。
  3. 数字和字符串:字符串转成数字再比。
  4. 布尔值参与比较:布尔值先转成数字(true → 1,false → 0)。
  5. 对象和原始值比较:对象先通过 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 == undefinedtrue唯一特例
null == 0falsenull 不参与数字转换
NaN == NaNfalseNaN 不等于任何值
Object.is(NaN, NaN)true专门处理 NaN 与正负零
0 == ''true'' → 0
'0' == falsetrue布尔先转数字,字符串再转数字
[] == falsetrue都转成 0
[] == ![]true![] 为 false,退化成上一行
{} == {}false比较引用地址

六、怎么选:默认只用 ===

  1. 默认全部用 ===,除非你有明确理由做类型转换。它可预测,不会因为隐式转换埋雷。
  2. 唯一建议用 == 的场景:同时判断 null 和 undefined,写 x == null 最简洁。
  3. 要区分 NaN 或正负零,用 Object.is。
  4. 如果确实需要比较不同类型的值,显式转换再比,别依赖 == 的隐式规则。
js
// 不推荐:依赖隐式转换,读代码的人要停下来推
if (inputValue == 0) { /* ... */ }

// 推荐:意图明确
if (Number(inputValue) === 0) { /* ... */ }

ESLint 的 eqeqeq 规则默认要求全部使用 ===,只在对比 null 时放行 ==,正好和上面的选型一致。

小结

  • === 不转换类型,类型不同直接 false;== 会先做强制转换再比。
  • == 的坑集中在布尔转换、字符串转数字、对象 ToPrimitive 三处。
  • null 只和 undefined 宽松相等,别的都 false;但关系运算符又会把 null 当 0。
  • NaN 和任何值都不相等,包括自己,要处理就用 Object.is。
  • 选型一句话:能 === 就 ===,只有判断 null/undefined 时用 ==。
评论
0/100