内容提要
文章对比了JavaScript中字符串转数值的9种方法,基准测试显示parseInt、parseFloat、Math.floor效率最低,仅为其他方法的约2%。作者通过分析V8、JavaScriptCore、QuickJS引擎源码,指出parseInt因参数校验、进制判断、字符串规整等前置操作而较慢。建议:输入可控且追求效率时用一元加号;需防御式编程时用parseInt或parseFloat;需支持BigInt时用Number。
延伸解读
性能差异的根源:前置校验与规整
基准测试显示 parseInt、parseFloat、Math.floor 的效率仅为其他方法的约 2%,其中 parseInt 最慢。文章通过分析 V8、JavaScriptCore、QuickJS 源码指出,parseInt 在真正转换前需进行参数校验、进制判断、字符串规整、越界判断等大量前置操作,最后才交由 runtime 处理。parseFloat 因标准流程更简单,前置操作较少,所以比 parseInt 快,但仍远低于一元加号等直接触发类型转换的运算符。
引擎实现风格差异不影响标准遵循
三个引擎对 parseInt 的实现均严格遵循 ECMAScript 标准,但代码风格迥异:V8 使用 Torque 编写的 Builtin 并配合 runtime 慢路径;JavaScriptCore 在 C++ 中逐步注释标准流程,可读性高;QuickJS 代码精炼、注释极少。文章指出,尽管风格不同,三者都因标准要求的前置操作而效率偏低。这提醒开发者,跨引擎的性能表现可能相似,选择方法时应优先考虑语义而非引擎差异。
按场景选择:效率、防御与 BigInt
文章建议,若输入可控且追求效率,优先使用一元加号(+),其标准简洁、引擎处理直接;若需防御式编程(输入不可信),使用 parseInt 或 parseFloat 以利用其严格的格式校验和进制处理;若需支持 BigInt,则使用 Number(),因为标准明确 Number 支持 BigInt 转换。使用双波浪线(~~)时需注意其 31 位限制,避免大整数溢出。这些建议基于各方法在引擎中的实际行为,而非单纯性能数字。
Q&A
JavaScript 里把字符串转成数字有哪些方法?性能差别大吗?
常见方法有 9 种:parseInt()、parseFloat()、Number()、双按位非(~~)、一元加号(+)、Math.floor()、乘以数字、有符号右移(>>)、无符号右移(>>>)。在 NodeJS V8 环境下,parseInt、parseFloat、Math.floor 的效率最低,只有其他运算的约 2%,其中 parseInt 最慢,仅约 1%。
为什么 parseInt 比一元加号慢那么多?
因为 parseInt 在真正转换前做了大量前置操作:参数合法性判断、默认值处理、进制判断、字符串格式判断与规整化、越界判断等,最后才交给 runtime 处理。而一元加号等运算符在引擎中直接进行数值类型转换,流程更简单,所以效率更高。
parseInt 和 parseFloat 在标准上有哪些区别?
根据 ECMAScript 标准,parseFloat 与 parseInt 有两点明显不同:parseFloat 仅支持一个入参,不支持进制转换;parseFloat 的返回值支持浮点型。
Number() 能处理 BigInt 吗?
可以。标准中明确说明 Number 支持 BigInt,各引擎实现也着重注意了这点。例如 V8 的 NumberConstructor 中会调用 ToNumber 并传入 BigIntHandling::kConvertToNumber,JavaScriptCore 中也会将 BigInt 转为 Number。
用 ~~ 把字符串转成整数有什么需要注意的?
使用双按位非(~~)运算符时需要注意 31 位问题。因为它会经过按位取反操作,涉及 Int32 转换,如果数值超出 31 位范围,结果可能不符合预期。
实际开发中应该怎么选择字符串转数值的方法?
如果只要求整型:追求简洁和效率且输入可控时优先用一元加号(+);需要防御式编程时用 parseInt();需要支持 BigInt 时优先用 Number(),若用 ~~ 需注意 31 位问题。如果要求浮点型:追求简洁和效率且输入可控时优先用一元加号(+);需要防御式编程时用 parseFloat();需要支持 BigInt 时用 parseFloat()。