类型转换与相等性全解析:==、===、ToPrimitive
面试必考的类型转换输出题:== 与 === 的区别、对象转原始值的 ToPrimitive 机制、 8 个假值,从规范推导 [] == ![] 为什么是 true。
一句话概括
JavaScript 是弱类型语言,运算和比较时经常”偷偷”帮你转换类型——这就是类型转换。面试官爱问它,因为它是输出题重灾区([] == ![] 到底等于什么?)也是线上 bug 的常见源头(接口返回的字符串数字和本地数字比较永远不等)。你不需要背下所有诡异结果,只需要掌握三条主线:显式转换的坑(Number/String/Boolean)、对象转原始值的 ToPrimitive 机制、== 宽松相等的五条规则。掌握这三条,任何输出题都能一步步推出来,而不是靠记答案。
核心知识点
1. 三种显式转换的特殊值(最容易翻车)
面试第一问通常是”JS 有哪几种类型转换”——答案是三种:转布尔、转数字、转字符串。但真正考的是特殊值:
1
2
3
4
5
6
7
8
9
10
11
12
13
Number(null); // 0 ← 记不住,最常考
Number(undefined); // NaN
Number(""); // 0
Number(" 12 "); // 12 ← 会去掉首尾空格
Number("12px"); // NaN ← 和 parseInt 不同,一个字符都不能错
Boolean("0"); // true ← 非空字符串都是 true
Boolean([]); // true ← 空数组也是 true!
Boolean({}); // true ← 空对象也是 true!
String(null); // "null"
String(undefined); // "undefined"
String([1, 2]); // "1,2" ← 数组转字符串用逗号拼接
记忆口诀:数字转换只有 4 个特殊值(null→0、”“→0、undefined→NaN、带空格数字→去空格),布尔转换只有 8 个假值,其余全是真。
2. 8 个假值(falsy),其余全是真值
布尔上下文(if、&&、||、!)里,只有 8 个值是假,记住它们就稳了:
| 假值 | 说明 |
|---|---|
false | 布尔假 |
0、-0 | 数字零 |
0n | BigInt 零(ES2020 新增,容易漏) |
"" | 空字符串 |
null | 空引用 |
undefined | 未定义 |
NaN | 非数字 |
1
2
3
4
5
// ❌ 以为空数组是假
if ([]) { console.log("执行"); } // 会执行![] 是真值
// ✅ 要判断"数组为空"得看长度
if (list.length === 0) { /* 真的空 */ }
面试高频追问:"0"、"false"、[]、{} 都是真值——因为它们是”非空字符串”或”对象”。
3. 对象转原始值:ToPrimitive 三步走
对象在需要变成原始值(数字/字符串)时,JS 会调用内部抽象操作 ToPrimitive,按 hint(期望类型)决定调用顺序:
1
2
hint = "number" 或 "default"(==、+ 号等)→ 先 valueOf(),不行再 toString()
hint = "string" (模板字符串、String()) → 先 toString(),不行再 valueOf()
每一步:Symbol.toPrimitive 优先级最高,有就直接用它;普通对象 valueOf() 默认返回自身(不是原始值,被跳过),所以最终几乎都落到 toString():
1
2
3
4
5
6
7
8
9
10
11
12
const obj = {};
obj.valueOf(); // {} ← 返回自身,不是原始值,被忽略
obj.toString(); // "[object Object]"
[].toString(); // "" ← 空数组
[1, 2].toString(); // "1,2"
({}).toString(); // "[object Object]"
// 面试名场面:怎么让 a == 1 && a == 2 && a == 3 成立?
// 利用 valueOf 每次比较都自增
const a = { value: 0, valueOf: () => ++a.value };
a == 1 && a == 2 && a == 3; // true!
两个特例记一下:Date 的 default 按 string 处理(先 toString);Symbol 永远只能显式转,"" + Symbol() 会直接报 TypeError。
4. == 宽松相等的五条规则(面试核心)
== 会做类型转换,按优先级排五条,从上到下命中一条就结束:
null == undefined→ true(唯一的例外,它俩”等价但不等型”)- 有一边是 boolean → 先把它转数字(
true→1、false→0) - 一边 string 一边 number → 字符串转数字
- 一边 object 一边 原始值 → 对象走 ToPrimitive(hint 为 default)
- 两边都是原始值且类型不同 → 按上述规则递归;对象 vs 对象 → 比较引用
用这套规则推导面试名场面 [] == ![]:
1
2
3
4
5
6
7
8
9
[] == ![]
// 1. 右边先算:![] 是真值取反 → false
[] == false
// 2. 规则 2:boolean 转数字 → false → 0
[] == 0
// 3. 规则 4:对象转原始值,[].toString() → ""(hint default 先 valueOf 失败再 toString)
"" == 0
// 4. 规则 3:字符串转数字 → Number("") → 0
0 == 0 // → true!
再验证几个经典结论:
1
2
3
4
null == undefined; // true,且这是 == 唯一推荐用法
null == 0; // false ← 别被 Number(null) === 0 迷惑,== 有专门规则
NaN == NaN; // false ← NaN 和任何值都不等,包括自己
{} == {}; // false ← 对象比较的是引用,两个不同对象永远不等
5. === 与 NaN 判断:工程实践怎么选
=== 不转换类型,类型不同直接 false。工程上默认用 ===,唯一的例外是判断 null/undefined:
1
2
3
4
5
6
// ✅ 工程最佳实践:用 === 判断空值
if (value === null || value === undefined) { /* 处理 */ }
// ✅ 更优雅的写法:x == null 同时覆盖 null 和 undefined
// (这是 == 唯一被官方推荐的用法,Airbnb 规范也认可)
if (value == null) { /* 处理 */ }
NaN 的判断是独立考点:
1
2
3
4
5
6
7
8
9
// ❌ 错误:NaN 不等于任何东西,包括自己
if (result === NaN) { } // 永远不成立
// ✅ 正确:Number.isNaN(不先做类型转换)
if (Number.isNaN(result)) { } // 只对真正的 NaN 返回 true
// 对比:全局 isNaN 会先把值转数字,'abc' 也会被判为 NaN
isNaN('abc'); // true ← 有坑
Number.isNaN('abc'); // false ← 推荐
其实你每天都在用
- 表单校验:
Number(input.value)拿到的是NaN而不是数字,所以用Number.isNaN判断输入是否合法。 - 接口返回的
"5"(字符串)和本地count + 1(数字)用==比较能通过,换成===就挂——这是线上最常见的”改了 === 出 bug”现场。 JSON.parse失败返回的是异常而不是 null,很多人误以为 parse 失败会得到 undefined。if (list.length)判断数组非空——length是 0 时是假,非 0 是真,能省一行代码。- 用户输入
"0"想表示”0 岁”,但if (age)把它当假值跳过了——所以判断”有没有填”要用!== ""。 input.value永远是字符串,input.value - 0或+input.value是转数字的老写法(但可读性差,推荐Number())。
常见误解(FAQ)
❌ 误区1:”== 和 === 只是”严格不严格”的区别,差不多。” 差很多:== 会做最多四层类型转换,[] == ![] 这种”看似荒谬”的结果全靠它。=== 类型不同直接 false,逻辑可预测。默认永远用 ===。
❌ 误区2:”Number(null) 是 0,所以 null == 0 也是 true。” 大坑!== 对 null/undefined 有专门规则:null == undefined 为 true,null == 0、null == "" 都是 false。显式转换和宽松相等是两套规则,别混。
❌ 误区3:”空数组 []、空对象 {} 是假值。” 恰恰相反,它们是真值。8 个假值里只有 false/0/-0/0n/""/null/undefined/NaN,其余全是真。判断”数组空不空”要看 length。
❌ 误区4:”判断 NaN 用 isNaN() 就行。” 全局 isNaN 会先把参数转数字,isNaN('abc') 返回 true(字符串被转成 NaN)。要用 Number.isNaN,它不做转换,只有真正的 NaN 才返回 true。
❌ 误区5:”a == 1 && a == 2 && a == 3 能成立是因为 JS 有 bug。” 不是 bug,是 ToPrimitive 的正常行为——valueOf 每次比较都会被调用,返回自增后的值。这也是面试官验证你”真懂对象转原始值”的经典考题。
一句话总结
类型转换就记三条:显式转换只有 4 个数字特殊值 + 8 个假值,对象转原始值靠 ToPrimitive(hint 决定 valueOf/toString 顺序),== 有”null/undefined 互等、布尔转数字、字符串转数字、对象转原始值”四条转换规则——默认用 ===,只有判断空值时用 == null。