💡 更多技术分享,欢迎访问我的博客:叁木の小屋
面试真题与最佳实践
本文是「JavaScript 类型转换详解」系列的第三篇,也是最后一篇。我们将通过面试题实战和最佳实践,帮你彻底掌握 JavaScript 类型转换。建议先阅读[基础篇](JavaScript 类型转换详解(一)- 基础篇本文是「JavaScript 类型转换详解」系列的第一篇,介绍类型转换 - 掘金)和[进阶篇](JavaScript 类型转换详解(二)- 进阶篇本文是「JavaScript 类型转换详解」系列的第二篇,深入探讨了常 - 掘金)。
目录
一、完整转换对照表
1.1 ToNumber 转换表
| 输入值 | 结果 |
|---|---|
| undefined | NaN |
| null | 0 |
| true | 1 |
| false | 0 |
| '' (空字符串) | 0 |
| ' ' (空白字符串) | 0 |
| '123' | 123 |
| '123abc' | NaN |
| '0x10' | 16 |
| Symbol | TypeError |
| 对象 | ToPrimitive -> ToNumber |
1.2 ToString 转换表
| 输入值 | 结果 |
|---|---|
| undefined | 'undefined' |
| null | 'null' |
| true | 'true' |
| false | 'false' |
| 123 | '123' |
| NaN | 'NaN' |
| Array | 元素用逗号连接 |
| Object | '[object Object]' |
| Function | 函数源代码字符串 |
1.3 ToBoolean 转换表
| 输入值 | 结果 |
|---|---|
| undefined | false |
| null | false |
| 0, -0, 0n | false |
| NaN | false |
| '' | false |
| 其他所有值 | true |
假值(falsy)只有 8 个:
false, 0, -0, 0n, '', null, undefined, NaN
1.4 == 宽松相等对照表
| undefined | null | Number | String | Boolean | Object | |
|---|---|---|---|---|---|---|
| undefined | true | true | ❌ | ❌ | ❌ | ❌ |
| null | true | true | ❌ | ❌ | ❌ | ❌ |
| Number | ❌ | ❌ | 值比较 | n == ToNumber(s) | n == ToNumber(b) | n == ToPrimitive(o) |
| String | ❌ | ❌ | ToNumber(s) == n | 值比较 | ToNumber(s) == ToNumber(b) | ToPrimitive(o) == ToNumber(s) |
| Boolean | ❌ | ❌ | ToNumber(b) == n | ToNumber(b) == ToNumber(s) | 值比较 | ToPrimitive(o) == ToNumber(b) |
| Object | ❌ | ❌ | ToPrimitive(o) == n | ToPrimitive(o) == ToNumber(s) | ToPrimitive(o) == ToNumber(b) | 引用比较 |
二、面试题实战
2.1 基础题
// Q1: 结果是什么?
[] + []
// 答案: ''
// 解析: [] -> '', '' + '' = ''
// Q2: 结果是什么?
[] + {}
// 答案: '[object Object]'
// 解析: [] -> '', {} -> '[object Object]'
// Q3: 结果是什么?
{} + []
// 答案: 0
// 解析: {} 被当作代码块,实际执行 +[] = 0
// Q4: 结果是什么?
1 + { a: 1 }
// 答案: '1[object Object]'
// 解析: {a:1} -> '[object Object]', '1' + '[object Object]'
2.2 进阶题
// Q5: 结果是什么?
let a = { x: 1 };
let b = { x: 1 };
a == b;
// 答案: false
// 解析: 对象按引用比较,不同对象不同引用
// Q6: 结果是什么?
let a = [1];
let b = [1];
a == b;
// 答案: false
// 解析: 数组也是对象,按引用比较
// Q7: 结果是什么?
[null] == "";
// 答案: false
// 解析: [null] -> 'null', 'null' != ''
// Q8: 结果是什么?
![] == [];
// 答案: true
// 解析: ![] -> false, [] -> '' -> 0, false -> 0, 0 == 0 = true
2.3 高难度题
// Q9: 经典题,如何让 a == 1 && a == 2 && a == 3 成立?
const a = {
i: 1,
valueOf() {
return this.i++;
}
}
a == 1 && a == 2 && a == 3
// 答案: true
// 解析: 每次 == 都调用 valueOf,i 递增
// Q10: Symbol.toPrimitive 版本
const a = {
[Symbol.toPrimitive](hint) {
return 42;
}
}
a == 42 // true
a === 42 // false
// 解析: == 会调用 toPrimitive,=== 不会
// Q11: 结果是什么?
[] == ![]
// 答案: true
// 解析: ![] -> false, [] -> '' -> 0, false -> 0, 0 == 0
// Q12: 结果是什么?
'0' == false // true
'0' === false // false
// 解析: == 时 false -> 0, '0' -> 0
// Q13: 结果是什么?
+new Date()
// 答案: 当前时间戳数字
// 解析: 一元 + 触发 ToPrimitive, Date 返回时间戳
2.4 经典"坑"题汇总
// 这些都是 true(可能让你意外)
[] == 0 // true
[] == false // true
[] == '' // true
0 == '' // true
0 == '0' // true
false == '0' // true
false == '' // true
null == undefined // true
// 这些都是 false(可能你以为 true)
[] == [] // false (不同引用)
{} == {} // false (不同引用)
NaN == NaN // false (NaN 不等于任何值)
null == 0 // false
undefined == 0 // false
// 链式比较的陷阱
1 < 2 < 3 // true (从左到右: (1<2)<3 = true<3 = 1<3 = true)
3 > 2 > 1 // false (从左到右: (3>2)>1 = true>1 = 1>1 = false)
// 正确的链式比较写法
1 < 2 && 2 < 3 // true
3 > 2 && 2 > 1 // true
2.5 面试题解题技巧
技巧 1:画转换流程图
// 解题步骤示例:[] == false
// 1. ToPrimitive([]) -> ''
// 2. ToNumber('') -> 0
// 3. ToNumber(false) -> 0
// 4. 0 == 0 -> true
技巧 2:记住几个特殊值
// 一定要记住这些特殊转换
[] -> '' -> 0
[1] -> '1' -> 1
[1,2] -> '1,2' -> NaN
{} -> '[object Object]' -> NaN
技巧 3:区分 == 和 ===
// === 永远更安全
// 只有面试题才会考 ==
三、最佳实践
3.1 避免隐式转换
// ❌ 不推荐
if (x == 1) { }
if (x) { } // 可能期望检查 undefined/null
// ✅ 推荐
if (x === 1) { }
if (x !== undefined && x !== null) { }
if (typeof x === "number" && !Number.isNaN(x)) { }
3.2 使用显式转换
// ❌ 隐式转换
const sum = a + b; // 可能是字符串拼接
// ✅ 显式转换
const sum = Number(a) + Number(b);
const sum = parseInt(a, 10) + parseInt(b, 10);
3.3 处理用户输入
// ❌ 不安全
const age = input;
if (age > 18) { } // input 是 '20abc' 时会得到 false
// ✅ 安全
const age = Number(input);
if (Number.isNaN(age)) {
// 处理无效输入
} else if (age > 18) {
// 有效输入
}
// 或者
const age = parseInt(input, 10);
if (isNaN(age)) {
// 处理无效输入
}
3.4 对象比较
// ❌ 直接比较
if (obj1 == obj2) { } // 比较引用
// ✅ 比较内容
if (JSON.stringify(obj1) === JSON.stringify(obj2)) { }
// 或使用 lodash isEqual
if (_.isEqual(obj1, obj2)) { }
3.5 使用 TypeScript
// TypeScript 可以帮助避免类型错误
function add(a: number, b: number): number {
return a + b; // 类型安全
}
add(1, 2); // ✅
add("1", 2); // ❌ 编译错误
3.6 常用工具函数
// 安全的数字转换
function toNumber(value) {
const num = Number(value);
return Number.isNaN(num) ? 0 : num;
}
// 检查是否为有效数字
function isValidNumber(value) {
return typeof value === "number" && !Number.isNaN(value);
}
// 深度比较(简单版)
function deepEqual(a, b) {
if (a === b) return true;
if (typeof a !== typeof b) return false;
if (typeof a !== "object" || a === null || b === null) return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every((key) => deepEqual(a[key], b[key]));
}
// 安全的加法
function safeAdd(a, b) {
const numA = Number(a);
const numB = Number(b);
if (Number.isNaN(numA) || Number.isNaN(numB)) {
return 0;
}
return numA + numB;
}
总结
JavaScript 的隐式类型转换是一个复杂但必须掌握的主题。
核心要点
- 优先使用 === 而不是 ==
- 了解 ToPrimitive 的转换顺序
- 记住假值列表(只有 8 个)
- 注意 null 和 undefined 的特殊性
- 使用显式转换避免意外
- 使用 TypeScript 获得类型安全
快速参考
// ToPrimitive 优先级
// Number hint: valueOf() -> toString()
// String hint: toString() -> valueOf()
// Date hint: 默认是 String
// == 的特殊情况
// null == undefined 是 true
// 其他情况 null/undefined 不转换
// 假值(8个)
// false, 0, -0, 0n, '', null, undefined, NaN
// + 运算符
// 一边是 string -> 拼接
// 否则 -> 加法
最后建议
虽然理解隐式转换很重要,但在实际编码中,应该始终使用显式转换和严格相等,这样可以避免很多难以调试的 bug。
希望这三篇文章能帮助你彻底掌握 JavaScript 类型转换!
参考资源: