返回
核心概念 · Core Concept

JavaScript
隐式转换 深度解析

== 到底做了什么?+ 为什么有时拼接字符串?本文将彻底揭开 JavaScript 类型强制转换(Type Coercion)的神秘面纱。

tricky.js
[] == ![]   // true 🤯
[] + []    //"" (空字符串)
true + true  // 2
基础规则

四种类型转换

在进行 ==+ 运算之前,JavaScript 引擎会先对操作数执行类型转换。理解这四种基础转换是掌握隐式转换的关键。

ToPrimitive

hint: "number"valueOf() → toString()
hint: "string"toString() → valueOf()
大多数对象valueOf() 返回自身
Date 对象toString() 优先

ToNumber

undefinedNaN
null0
true / false1 / 0
""0
"123"123
"abc"NaN

ToString

undefined"undefined"
null"null"
true / false"true"/"false"
123"123"
NaN"NaN"
[1,2]"1,2"

ToBoolean

falsefalse
0, -0, 0nfalse
""false
nullfalse
undefinedfalse
NaNfalse

假值军团 — Falsy Values

只有以下 8 个 值转换为 false,其他一切都是 true

false
0
-0
0n
""
null
undefined
NaN

常见陷阱:"0"[]{}function(){} 都是 truthy

Abstract Equality

== 比较算法

==(宽松相等)的执行逻辑由 ECMAScript 规范定义。下面是简化后的核心步骤,按优先级排序:

1

类型相同?→ 直接用 ===

如果两个操作数的类型相同,== 的行为与 === 完全一致,不会进行任何转换。

1 === 1          // true
"hi" === "hi"    // true
NaN === NaN      // false  ← NaN 不等于自身!
2

null 和 undefined 互相相等

这是规范中的特例:null undefined== 比较时返回 true,且不与其他任何值相等。

null == undefined   // true
null == 0           // false
undefined == ""     // false
null == null        // true
3

数字 vs 字符串 → 转字符串为数字

如果一边是数字,另一边是字符串,会将字符串通过 ToNumber() 转换后再比较。

0 == ""        // ToNumber("") → 0,   0 == 0 → true
0 == "0"       // ToNumber("0") → 0,  0 == 0 → true
1 == "1"       // ToNumber("1") → 1,  1 == 1 → true
NaN == "NaN"   // ToNumber("NaN") → NaN, NaN == NaN → false
4

布尔值参与?→ 先转成数字

如果有一边是布尔值,会先将其转为数字(true → 1false → 0),然后再按后续规则继续比较。

true == 1      // true → 1,  1 == 1 → true
false == 0     // false → 0, 0 == 0 → true
true == "1"    // true → 1,  "1" → 1,  1 == 1 → true
false == ""    // false → 0, "" → 0,   0 == 0 → true
false == "0"   // false → 0, "0" → 0,  0 == 0 → true

关键记忆:布尔值在这一步永远先变成 0 或 1,然后走数字比较路线。 所以 true == "1"true,但 true === "1"false

5

对象 vs 原始值 → 对象执行 ToPrimitive

如果一边是对象,另一边是原始值,会对对象执行 ToPrimitive()(默认 hint 为 "number"),然后继续比较。

对于普通对象,valueOf() 返回对象自身(不是原始值),所以会继续调用 toString()

// 数组的 toString 特殊行为
[].toString()        // ""        (空数组 → 空字符串)
[1, 2].toString()    // "1,2"     (逗号连接)
[null].toString()    // ""        (null → "")

// 普通对象
{}.toString()        // "[object Object]"
6

都不匹配?→ 返回 false

如果经过以上所有规则都无法匹配(例如 Symbol 类型参与比较),则直接返回 false

Symbol("a") == Symbol("a")   // false (不同 Symbol)
0 == Symbol("0")              // TypeError! (不能转为数字)
完整推演

为什么 [] == ![] 为 true?

原始表达式
[] == ![]

先计算右侧 ![]

① 求值右侧
![]false

[] 是对象,truthy → 取反 → false

② 现在表达式
[] == false

规则④:布尔值参与 → 转为数字

③ 布尔转数字
[] == ToNumber(false)[] == 0

false → 0

④ 对象转原始值
ToPrimitive([]) == 0

[].valueOf() → [] (非原始) → [].toString()

⑤ 数组 toString
"" == 0

[].toString() → ""(空字符串)

⑥ 字符串转数字
ToNumber("") == 00 == 0

"" → 0

⑦ 最终结果
0 == 0✅ true

类型相同,直接比较

Addition Operator

+ 运算符的双面性

+ 是 JavaScript 中最特殊的运算符 —— 它既能做数学加法,也能做字符串拼接。 核心规则只有一条:任一操作数是字符串就拼接,否则做加法

执行流程

1

ToPrimitive 两个操作数

对象先转原始值(valueOf → toString)

2

有字符串吗?

任一侧是字符串 → 字符串拼接
否则 → ToNumber 后数学相加

字符串拼接路径
1 + "2""12"
"hi" + true"hitrue"
"a" + null"anull"
"" + []""
数学加法路径
1 + 23
true + true2
null + 11
false + 00

对比:- 运算符永远是数学运算

"5" - 23

字符串转数字后减

"5" + 2"52"

字符串拼接

"abc" - 1NaN

"abc" → NaN

经典面试题

那些令人困惑的陷阱

空数组的秘密
[] == ![]
结果true

![] 先求值为 false,然后 [] == false,布尔转数字 → [] == 0,对象 ToPrimitive([]) → "",字符串转数字 → 0 == 0 → true

空数组相加
[] + []
结果""

两个数组 ToPrimitive 后都变成空字符串 "",两边都是字符串,拼接后仍是空字符串。

对象加数组
{} + []
结果"[object Object]"

{}.toString() 为 "[object Object]",[].toString() 为 "",字符串拼接。注意:在某些环境下 {} 在行首会被解析为代码块!

香蕉
"b" + "a" + + "a" + "a"
结果"baNaNa"

一元 + 将 "a" 转为数字 → NaN,然后字符串拼接:"" + "a" + NaN + "a" → "baNaNa"

null 的沉默
null + 1
null + 0
结果1 / 0

null 通过 ToPrimitive 仍是 null,+ 右侧不是字符串,ToNumber(null) → 0,所以 0+1=1,0+0=0。

undefined 的无
undefined + 1
undefined == null
结果NaN / true

ToNumber(undefined) → NaN,NaN + 1 = NaN。而 undefined == null 是规范特例,直接返回 true。

速查表

一图掌握核心规则

表达式
结果
关键规则
null == undefined
true
规范特例
NaN == NaN
false
NaN 不等于自身
0 == ""
true
"" → 0
0 == "0"
true
"0" → 0
"" == "0"
false
同类型,直接比
false == "0"
true
false→0, "0"→0
false == null
false
null 只和 undefined 相等
"\t" == 0
true
空白串 → 0
[] == 0
true
[]→""→0
[1] == 1
true
[1]→"1"→1
"" == []
true
[]→"",同类型比
0 == []
true
[]→""→0

终极建议

在实际开发中,始终使用 ===(严格相等)进行比较。隐式转换是 JavaScript 的历史包袱,理解它的机制是为了更好地避开陷阱,而不是利用它写出令人困惑的代码。

ESLint eqeqeqTypeScript代码审查
参考规范:ECMA-262 §7.2.14 Abstract Equality Comparison