JS中toString()、String()和模板字面量转换数字的区别

作者:小满

时间:2026-11-04

浏览:308

深入对比JavaScript中将数字转换为字符串的三种主要方式:Number.prototype.toString()、String()和模板字面量${val},解析底层ECMAScript规范、词法解析陷阱、基数进制支持及空值安全边界。

文章目录

在 JavaScript 开发中,将数字转换为字符串是最基础且频繁的操作之一。无论是格式化订单编号、拼接接口 URL 查询参数,还是向界面渲染带单位的数据,开发者通常会在 num.toString()、String(num) 和模板字面量 `${num}` 之间做出选择。虽然在传入常规正整数(如 123)时三者都会输出相同的字符串 "123",但在原型方法与全局函数机制、进制参数支持、空值(null / undefined)容错处理以及词法解析规则上,它们存在根本性的技术差异。本文将拆解这三种方式的核心机制与边界细节,帮助读者建立清晰准确的技术选型依据。

JS中toString()、String()和模板字面量转换数字的区别
图 1:JavaScript 中三种数字转字符串机制全景架构与关键特性对比

一、核心机制与基本用法剖析

为了理解差异,首先需要弄清这三种转换途径在 ECMAScript 规范中分别扮演的角色与底层工作原理。

1. Number.prototype.toString([radix]):面向对象的原型方法

toString() 是定义在 Number.prototype 上的原型链方法。当我们在一个数字变量上调用 n.toString() 时,JavaScript 引擎会先执行装箱操作(即创建对应的 Number 包装对象),随后沿原型链找到 Number.prototype.toString 并完成转换。

该方法最大的技术特色是支持基数(radix)参数,其取值范围为 2 到 36。这使得它不仅能输出常规十进制文本,还是实现跨进制计算与字符转化的核心原生手段:

const count = 255;

// 默认输出十进制
console.log(count.toString());     // 示例输出: "255"

// 转换为二进制与十六进制字符串
console.log(count.toString(2));   // 示例输出: "11111111"
console.log(count.toString(16));  // 示例输出: "ff"
console.log(count.toString(36));  // 示例输出: "73" (0-9 + a-z)
语法注意:对字面量直接调用 toString() 时,不能直接书写 42.toString()。因为 JavaScript 词法解析器会将数字后的首个小数点 . 解析为浮点字面量的小数点,遇到后续字母即报词法语法错误(SyntaxError)。

2. String(value):遵循 ECMAScript ToString 规范的全局函数

String() 是 JavaScript 的全局构造函数,但当作为普通函数直接调用(不加 new 操作符)时,它承担的是显式类型强制转换的功能。根据 ECMAScript 规范,String(value) 底层直接调用内部抽象操作 ToString(argument)。

与原型方法不同,String() 不需要先将数值装箱为对象,因此它具备天然的空值容错能力:即使传入的是 null 或 undefined,也不会抛出异常,而是严格按照规范返回 "null" 或 "undefined":

const score = 98.5;
console.log(String(score));       // 示例输出: "98.5"

// 空值防御测试
console.log(String(null));        // 示例输出: "null" (不抛错)
console.log(String(undefined));   // 示例输出: "undefined" (不抛错)

3. 模板字面量 `${value}`:ES6 原生字符串插值

ES6 引入的模板字面量语法糖主要用于表达式嵌入与多行文本排版。当把一个数值放入占位符 ${...} 中时,JavaScript 引擎会先对花括号内的表达式求值,接着自动执行规范规定的抽象操作将其隐式转换为字符串,再与周围的字符串片段拼接。

当仅包含一个表达式时(例如 `${val}`),其转换规则与 String(val) 高度契合,但在语义上更强调“构建动态文本”:

const port = 8080;

// 单值插值转换
console.log(`${port}`);               // 示例输出: "8080"

// 复合文本拼接场景
console.log(`Server on port: ${port}`); // 示例输出: "Server on port: 8080"
JS中toString()、String()和模板字面量转换数字的区别
图 2:JavaScript 词法分析器点号歧义机制与 ECMAScript ToString 内部规范调用链路

二、三大维度的关键区别对比

在日常编码中,这三种方式在语法限制、空安全保障和进制能力上存在显著分水岭。

1. 进制转换能力(基数 radix 参数)

只有 Number.prototype.toString([radix]) 接受 radix 参数,能够在 2 到 36 之间自由指定基数。而 String() 与模板字面量属于纯粹的字符串形态化工具,完全不具备基数转换能力:

const rgb = 255;

// 需求:生成 CSS 十六进制色值
const hex = rgb.toString(16).padStart(2, '0'); // "ff"

// String() 和模板字面量无法指定进制:
console.log(String(rgb));    // 恒为十进制 "255"
console.log(`${rgb}`);       // 恒为十进制 "255"

2. 空值安全性与异常边界(null 与 undefined)

在前后端数据交互中,后端接口返回的数字字段可能存在缺省或空值(例如 null 未设置或 undefined)。此时三种方式的健壮性表现截然不同:

  • toString() 会导致系统崩溃:由于 null 和 undefined 属于原始类型且无法包装为对象,对其访问 .toString() 会立即抛出致命的 TypeError: Cannot read properties of null (reading 'toString')。
  • String() 与模板字面量具备安全兜底:两者均能安全执行并分别输出 "null" 与 "undefined",不会中断程序主流程。
function formatUserId(id) {
  // 危险写法:若 id 为 null,直接引发运行时崩溃!
  // return id.toString(); // TypeError!

  // 安全防御写法:
  return String(id); // 安全返回 "null",应用稳定不崩溃
}

3. 数字字面量的语法解析陷阱(点号二义性)

当开发者尝试对字面量数字直接调用 toString() 时,经常遭遇意外报错:

// 错误示范:
42.toString(); // SyntaxError: Invalid or unexpected token

// 正确做法 1:通过分组括号划分表达式优先级(最推荐,语义清晰)
(42).toString();

// 正确做法 2:利用双点语法(首点表示小数点 42.0,次点表示属性访问)
42..toString();

// 正确做法 3:使用空格分隔词法 Token
42 .toString();

// 若采用 String() 或模板字面量,则从语法结构上彻底规避此问题:
String(42);    // 结构清晰,无点号二义性
`${42}`;       // 直观干净
JS中toString()、String()和模板字面量转换数字的区别
图 3:实际开发中数字转字符串的技术选型决策路径流程图

三、特殊数值的表现细节

除了常规数值,JavaScript 引擎在处理特殊数值(特殊浮点数、负零、大数)时的具体表现同样值得关注:

1. 负零值(-0)的符号位丢失

在 IEEE 754 浮点数标准中,-0 与 +0 内部存储不同,但通过 toString()、String() 以及模板字面量进行类型转换时,三者都遵循统一规范,输出正零字符串 "0",负号不会被保留:

console.log((-0).toString()); // "0"
console.log(String(-0));     // "0"
console.log(`${-0}`);        // "0"

2. 极值与科学计数法表示

对于极大数(绝对值大于等于 10^21)或极小数(绝对值小于等于 10^-6),三种转换方式均按照 ECMAScript 的 Number::toString 算法自动输出指数形式字符串:

const large = 1e21;
const tiny = 0.0000001;

console.log(large.toString());  // "1e+21"
console.log(String(tiny));     // "1e-7"
console.log(`${tiny}`);        // "1e-7"

3. BigInt 大整数类型

对于 ES2020 引入的原生 BigInt(例如 1234567890123456789n),三种方式都可以正常转为不带末尾 n 字符的纯数字字符串:

const big = 123456789n;
console.log(big.toString()); // "123456789"
console.log(String(big));    // "123456789"
console.log(`${big}`);       // "123456789"
JS中toString()、String()和模板字面量转换数字的区别
图 4:全场景边界输入与三大转换方式行为表现对照表

四、实际开发中的最佳实践与选型指南

根据团队协作与工程维护经验,建议遵循以下清晰的选型步骤:

  1. 需要进制计算时(二进制、十六进制):毫不犹豫使用 Number(val).toString(radix)。这是唯一的标准解法。建议在调用前配合可选链或类型守卫(如 val?.toString(16))避免空指针。
  2. 在复合句子或排版中拼接数字:优先使用模板字面量 `Total: ${count} items`。其语义连贯、易于阅读,且避免了加号拼接导致的运算符优先级陷阱。
  3. 用于不可控变量、函数参数的防御性转换:优先使用 String(val)。当数据来源于外部接口、路由参数或用户输入,存在 null 或 undefined 风险时,String() 是最稳健的防御手段。
  4. 在纯数组函数式映射中:使用 String 作为一等公民函数传入,写法高度凝练:
    const numbers = [1, 2, 3];
    // 优雅的函数式映射:
    const strings = numbers.map(String); // ["1", "2", "3"]

五、常见问题排查与注意事项

问:为什么不推荐用隐式转换 num + "" 来转字符串?

答:虽然 num + "" 在写法上很短,但它涉及隐式强制类型转换。当操作数并非纯数字而是复杂对象或带有重写 valueOf 的实例时,隐式加法的计算行为极易引发难以排查的 Bug。此外,BigInt 类型与字符串直接相加能够拼接,但如果与其他原始类型混淆相加可能抛出 TypeError: Cannot mix BigInt and other types,代码意图远不如 String(num) 显式清晰。

问:Symbol 类型在三者转换中表现相同吗?

答:不同。虽然本文聚焦数字转换,但了解该边界有助于加深理解:String(Symbol('id')) 会安全输出 "Symbol(id)",而模板字面量 `${Symbol('id')}` 会直接抛出 TypeError: Cannot convert a Symbol value to a string。这进一步印证了 String() 是整个语言规范中宽容度最高、安全系数最强的转换途径。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章