
配置文本里写着 1_000,执行 Number(value) 却得到 NaN。下划线明明是 JavaScript 的数字分隔符,怎么到了转换时就不认了?关键在于它属于源码的词法写法,不是数字字符串的通用格式。
下划线在哪一层生效
源文件里的 1_000 会在代码解析阶段被识别成一个数值字面量,求值结果是普通的 1000。下划线不进入数值本身,也不会附着在运行时数字上。它能出现在十进制数值字面量中,也适用于其他进制,例如 0xCA_FE,还可用于 BigInt 字面量。分隔符的作用是让源码更好读,不是让所有接收数字的接口都支持这种拼法。说白了,它由语法解析器消费,运行时值里只留下数字;输出中的十进制 1000000 和十六进制 51966 已经看不到原先的下划线。
这条边界很容易在"源码常量"和"外部数据"之间被混淆。下面两种写法看起来相近,处理阶段却不同:
const sourceNumber = 1_000;
const inputText = "1_000";
console.log(sourceNumber);
console.log(Number(inputText));
前者在程序运行前就已经完成语法解析,后者只是一个包含数字和下划线的字符串。Number() 按字符串到数字的转换规则处理它,不会先把字符串改写成合法的 JavaScript 源码。Node.js 26.8.1 的实际输出是:
node v26.8.1
1000000 literal: 1000000
hex literal: 51966
bigint literal: 12345
eval "1_000": 1000
eval "1__000": SyntaxError
eval "_1000": ReferenceError
eval "1000_": SyntaxError
eval "1e1_0": 10000000000
Number("1_000"): NaN
parseInt("1_000", 10): 1
Number("12_345n"): NaN
parseInt("12_345n", 10): 12
1_2 + 3_4: 46
因此,不能把"JS 代码允许这种写法"推成"运行时字符串也允许这种写法"。如果配置格式明文规定千位分组下划线,解析器需要按该格式验证并移除分隔符;若格式没有这样的约定,直接把下划线当成可忽略字符,可能会把拼错的输入也收下。
分隔符还有语法位置
分隔符不是任意插进数字中的装饰符。连续两个下划线、紧挨数字字面量结尾等写法不符合数值字面量语法。脚本用 eval() 对文本逐个解析,合法的 1_000 和 1e1_0 能得到数值;1__000 与 1000_ 则抛出 SyntaxError。_1000 是另一回事:它是标识符表达式,解析成功后运行时报 ReferenceError,不能把它也归为数字分隔符的语法错误。
小数点等记号周围也受词法规则约束。记忆一张"下划线可放的位置表"不如记住这是语言解析器的规则:需要判断某种具体拼法时,写一个最小脚本交给当前目标环境解析。编译器或运行时会告诉结果,猜规则容易把"表达式里有下划线"和"数字字面量允许下划线"混为一谈。
parseInt 看起来接受了,其实截断了
同一组输出里,parseInt("1_000", 10) 得到 1。这不表示 parseInt 支持分隔符。它从字符串开头读取可识别的十进制数字,遇到下划线时停止;对 "12_345n" 也会停在 n 前,结果是 12。所以这里没有错误提示,反而更容易让错误配置悄悄变成错误数值。
Number() 对 "1_000" 返回 NaN,更适合发现整个字符串不符合数字转换语法,但调用方仍要显式检查 Number.isNaN() 或使用 Number.isFinite()。不能假定转换结果有效。对于 BigInt,字面量 12_345n 在源码中可用;字符串 "12_345n" 既带分隔符,也带字面量专用的 n 后缀,不能直接作为 Number() 的输入。要从文本构造 BigInt,应先约定输入格式,再调用 BigInt(),它也不接受任意格式化文本。
我会把分隔符留在代码常量里,不把它默认为用户输入协议的一部分。若界面确实允许 1_000 这种格式,先完整校验分组位置,再规范化成 1000,随后检查范围和整数性;不要用 parseInt 的部分读取来代替验证。源码可读性与输入容错是两项不同的设计决定。