JavaScript 类型转换详解
JavaScript 类型转换详解
Introduction
JavaScript 是一门弱类型语言,变量在赋值时可以动态改变类型,而在表达式求值过程中,类型转换也无处不在。理解类型转换的规则,是写出健壮 JavaScript 代码的基础。无论是字符串与数字的拼接、条件判断中的隐式转换,还是 == 与 === 的差异,背后都涉及 JavaScript 的类型转换机制。稍有疏忽,就会出现诸如 "1" + 2 结果为 "12" 而非 3 这样的"奇怪"现象。本文将系统讲解 JavaScript 类型转换的两大类别——隐式转换与显式转换,从底层原理到日常实践,帮助你彻底掌握这一核心知识点。
基础语法
一、显式类型转换(Explicit Type Conversion)
显式转换指开发者主动调用转换函数,明确告知 JavaScript 进行类型转换。
1. 转为字符串
String(123); // "123"
String(true); // "true"
String(null); // "null"
String(undefined); // "undefined"
String({a: 1}); // "[object Object]"
String([1, 2, 3]); // "1,2,3"
// 或使用 .toString()
(123).toString(); // "123"
true.toString(); // "true"
2. 转为数字
Number("123"); // 123
Number("12.34"); // 12.34
Number(""); // 0
Number(" 42 "); // 42(自动忽略首尾空格)
Number("abc"); // NaN
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
Number({}); // NaN
// parseInt / parseFloat(更宽松,从左到右解析)
parseInt("42px"); // 42
parseInt("3.14km"); // 3
parseFloat("3.14km"); // 3.14
parseInt("abc"); // NaN
3. 转为布尔值
Boolean(1); // true
Boolean(0); // false
Boolean(""); // false
Boolean("abc"); // true
Boolean(null); // false
Boolean(undefined); // false
Boolean({}); // true(对象永远为 true)
Boolean([]); // true(空数组也为 true)
4. 转为整数或浮点数(进阶)
parseInt("42", 10); // 42(十进制)
parseInt("101", 2); // 5(二进制转十进制)
parseInt("0xFF", 16); // 255(十六进制)
Number("42.5"); // 42.5
Math.floor(42.9); // 42
Math.round(42.5); // 43
Math.ceil(42.1); // 43
Math.trunc(42.9); // 42(直接截断)
二、隐式类型转换(Implicit Type Conversion / Coercion)
隐式转换由 JavaScript 引擎在特定操作中自动触发,常见于运算符求值过程中。
1. 加法运算符 + 的字符串偏置
"5" + 3; // "53"(数字被转为字符串)
5 + "3"; // "53"
"5" + true; // "5true"
"5" + null; // "5null"
"5" + undefined; // "5undefined"
2. 减法、乘法、除法运算符 — 转数字
"6" - 2; // 4(字符串转为数字)
"6" * "3"; // 18
"6" / "2"; // 3
"abc" - 1; // NaN
true - false; // 1(布尔转为数字)
null - 5; // -5(null 转为 0)
undefined - 5; // NaN
3. 比较运算符
// 数字 vs 字符串:字符串转数字
"5" > 3; // true
"10" < 9; // false("10" 转为 10)
// 布尔 vs 其他:布尔转数字
true > 0; // true
false == 0; // true(宽松相等)
false === 0; // false(严格相等,类型不同)
// null 和 undefined
null == undefined; // true(宽松相等,特殊规则)
null === undefined; // false
null > 0; // false
null >= 0; // true(null 转为 0)
4. 宽松相等 == 的类型转换规则
// 常见 == 转换陷阱
"0" == false; // true("0" 转数字 0,false 转数字 0)
"" == false; // true(空字符串转 0)
[] == false; // true(数组转字符串 "",再转 0)
[] == ""; // true
{} == false; // false(对象转字符串 "[object Object]")
{} == "[object Object]"; // true
代码示例
示例 1:强制类型转换函数对比
const values = [0, 1, "", "hello", null, undefined, NaN, [], {}, Infinity];
console.log("--- Number() ---");
values.forEach(v => console.log("Number(" + JSON.stringify(v) + ") = " + Number(v)));
console.log("\n--- Boolean() ---");
values.forEach(v => console.log("Boolean(" + JSON.stringify(v) + ") = " + Boolean(v)));
示例 2:巧用隐式转换
// 利用 + "" 将任意值转为字符串
const num = 42;
const str = num + ""; // "42"
// 利用 - 0 将字符串转为数字
const strNum = "3.14";
const num2 = strNum - 0; // 3.14
// 利用 !! 将任意值转为布尔
const maybe = "maybe";
const bool = !!maybe; // true
// 利用 | 0 将浮点数转为整数(截断)
const pi = 3.14159;
const int = pi | 0; // 3
示例 3:parseInt 进制转换
// 二进制字符串转十进制
parseInt("1101", 2); // 13
// 十六进制字符串转十进制
parseInt("FF", 16); // 255
// 八进制字符串转十进制
parseInt("77", 8); // 63
// 处理带前缀的进制字符串
parseInt("0xFF"); // 255(自动识别十六进制)
示例 4:Symbol 不能转为数字
Number(Symbol("id")); // TypeError: Cannot convert a Symbol value to a number
String(Symbol("id")); // "Symbol(id)"
Boolean(Symbol("id")); // true(Symbol 对象转为 true)
示例 5:BigInt 类型转换
// BigInt 不能与普通 Number 混合运算
Number(123n); // 123
BigInt(123); // 123n
// BigInt 整数除法会丢失小数
console.log(Number(1n / 3n)); // 0(丢失小数部分)
运行效果
--- Number() 转换结果 ---
Number(0) = 0
Number(1) = 1
Number("") = 0
Number("hello") = NaN
Number(null) = 0
Number(undefined) = NaN
Number(NaN) = NaN
Number([]) = 0
Number({}) = NaN
Number(Infinity) = Infinity
--- Boolean() 转换结果 ---
Boolean(0) = false
Boolean(1) = true
Boolean("") = false
Boolean("hello") = true
Boolean(null) = false
Boolean(undefined) = false
Boolean(NaN) = false
Boolean([]) = true(空数组为 true!)
Boolean({}) = true
Boolean(Infinity) = true
--- parseInt 进制转换 ---
parseInt("1101", 2) = 13
parseInt("FF", 16) = 255
--- 隐式转换示例 ---
"5" + 3 = "53"
"6" - 2 = 4
"abc" - 1 = NaN
true - false = 1
null - 5 = -5
!!"maybe" = true
3.14159 | 0 = 3
常见问题
Q1: == 和 === 的区别是什么?
===(严格相等)不进行类型转换,只有类型和值同时相等才返回 true。==(宽松相等)会先进行类型转换,再比较值。由于隐式转换规则复杂且容易出错,推荐始终使用 ===,只在明确需要类型转换的场景下使用 ==。
Q2: NaN 是什么?如何判断 NaN?
NaN(Not a Number)表示"不是数字"的计算结果,但它本身类型仍然是 Number。NaN 是唯一一个与自身不相等的值。
NaN === NaN; // false(坑!)
isNaN(NaN); // true
isNaN("abc"); // true("abc" 被转数字)
Number.isNaN(NaN); // true
Number.isNaN("abc"); // false(更精确,只有真正的 NaN 才返回 true)
Q3: null 和 undefined 的区别?
- null:表示"已赋值为空",是一个被显式设置的对象引用。
- undefined:表示"未定义",变量声明但未赋值,或对象属性不存在。
let a;
console.log(a); // undefined(声明未赋值)
const b = null;
console.log(b); // null(显式赋空)
console.log(typeof null); // "object"(历史遗留 bug)
Q4: 空数组 == 空字符串 == 0?
Number([]); // 0(空数组先 toString 为 "",再转数字)
Number(""); // 0
[] == ""; // true
0 == ""; // true
// 所以 [] == 0 == "",三个值宽松相等
Q5: parseInt 和 Number 的区别?
- Number():严格转换,整个字符串必须是合法的数字,否则返回 NaN。
- parseInt/parseFloat():宽松转换,从左到右解析,遇到非数字字符停止。
Number("42px"); // NaN
parseInt("42px"); // 42
parseFloat("3.14km"); // 3.14
Q6: 为什么 typeof null 是 "object"?
这是 JavaScript 早期的设计缺陷。在 JS 最初的实现中,对象的内部类型标签是 0,null 的指针正好是 0x00,因此被错误地识别为 object 类型。这个 bug 已经存在了 20+ 年,由于兼容性原因至今未被修复。
Q7: 如何安全地进行类型转换?
// 安全转数字:先判断是否为数字字符串
function toNumber(value) {
if (typeof value === "number" && !isNaN(value)) {
return value;
}
if (typeof value === "string" && /^\s*-?\d+(\.\d+)?\s*$/.test(value)) {
return Number(value);
}
return NaN;
}
// 安全转布尔
const safeBool = !!value;
延伸阅读
- MDN - Type Conversion:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Type_conversion
- MDN - Equality (==, ===, Object.is):详细对比三种比较方式
- You Don't Know JS - Types and Grammar(Kyle Simpson):深入剖析 JavaScript 类型系统的经典书籍
- Why typeof null is "object":2ality 博客的历史分析
- BigInt:ES2020 引入的新类型,用于处理超过 Number 安全整数范围的数值