JavaScript 类型转换详解(三)- 实战篇

51 阅读5分钟

💡 更多技术分享,欢迎访问我的博客:叁木の小屋

面试真题与最佳实践

本文是「JavaScript 类型转换详解」系列的第三篇,也是最后一篇。我们将通过面试题实战和最佳实践,帮你彻底掌握 JavaScript 类型转换。建议先阅读[基础篇](JavaScript 类型转换详解(一)- 基础篇本文是「JavaScript 类型转换详解」系列的第一篇,介绍类型转换 - 掘金)和[进阶篇](JavaScript 类型转换详解(二)- 进阶篇本文是「JavaScript 类型转换详解」系列的第二篇,深入探讨了常 - 掘金)。

目录

  1. 完整转换对照表
  2. 面试题实战
  3. 最佳实践

一、完整转换对照表

1.1 ToNumber 转换表

输入值结果
undefinedNaN
null0
true1
false0
'' (空字符串)0
' ' (空白字符串)0
'123'123
'123abc'NaN
'0x10'16
SymbolTypeError
对象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 转换表

输入值结果
undefinedfalse
nullfalse
0, -0, 0nfalse
NaNfalse
''false
其他所有值true

假值(falsy)只有 8 个:

false, 0, -0, 0n, '', null, undefined, NaN

1.4 == 宽松相等对照表

undefinednullNumberStringBooleanObject
undefinedtruetrue
nulltruetrue
Number值比较n == ToNumber(s)n == ToNumber(b)n == ToPrimitive(o)
StringToNumber(s) == n值比较ToNumber(s) == ToNumber(b)ToPrimitive(o) == ToNumber(s)
BooleanToNumber(b) == nToNumber(b) == ToNumber(s)值比较ToPrimitive(o) == ToNumber(b)
ObjectToPrimitive(o) == nToPrimitive(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 的隐式类型转换是一个复杂但必须掌握的主题。

核心要点

  1. 优先使用 === 而不是 ==
  2. 了解 ToPrimitive 的转换顺序
  3. 记住假值列表(只有 8 个)
  4. 注意 null 和 undefined 的特殊性
  5. 使用显式转换避免意外
  6. 使用 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 类型转换!


参考资源: