先记住这个答案
一元加号 +x 和 Number(x) 在规范里调用的是同一个抽象操作 ToNumber,所以对字符串、布尔、null、undefined、数组和普通对象,结果完全一样。空字符串两者都转成 0,'12px' 两者都是 NaN。唯一需要记住的差别是 BigInt:Number(10n) 得到 10,+10n 会抛 TypeError。日常开发中两者可以互换,团队代码里更推荐写 Number(x),因为一个孤零零的加号很容易在阅读时被忽略。
+x和Number(x)转换规则相同,结果一致- 空串、空白串转成 0,
'12px'转成NaN - 一元加号不能直接处理 BigInt
- 带单位文本可用
parseFloat解析前缀
常见输入的转换结果
把一组典型输入分别用两种写法转一遍,放在一起对照,就能看出它们是同一套规则。
const inputs = ['42', ' 3.5 ', '', '12px', '0x1f', '1e3', null, undefined, true, [], [7], {}]
for (const v of inputs) {
// 第一列是原值,后两列分别是 +v 和 Number(v)
console.log(JSON.stringify(v), '|', +v, '|', Number(v))
}查看输出与解释
"42" | 42 | 42
" 3.5 " | 3.5 | 3.5
"" | 0 | 0
"12px" | NaN | NaN
"0x1f" | 31 | 31
"1e3" | 1000 | 1000
null | 0 | 0
undefined | NaN | NaN
true | 1 | 1
[] | 0 | 0
[7] | 7 | 7
{} | NaN | NaN字符串的规则是:先去掉首尾空白,剩下的必须整体是一个合法数字字面量,否则就是 NaN。所以 ' 3.5 ' 能转,'12px' 不行。空字符串和纯空白字符串是特例,都转成 0,这在处理表单输入时经常坑人:用户什么都没填,你却拿到了一个合法的 0。
数组和对象会先转成原始值。[] 转成空字符串再变成 0,[7] 转成 '7' 再变成 7,{} 转成 '[object Object]',只能是 NaN。
唯一的差别:BigInt
BigInt 是 ES2020 加入的任意精度整数类型。规范在设计时刻意没让一元加号支持它,原因是 asm.js 这类老代码用 +x 来表示「这一定是个 Number」,如果 + 对 BigInt 静默成功,就破坏了这个约定。
console.log(Number(10n)) // BigInt 可以显式转成数字
try {
console.log(+10n)
} catch (e) {
console.log(e.name) // 一元加号不接受 BigInt
}
console.log(Number(), +undefined) // 不传参是 0,undefined 是 NaN
console.log(typeof Number('5'), typeof new Number('5'))查看输出与解释
10
TypeError
0 NaN
number object所以如果某个函数可能收到 BigInt,比如处理后端返回的大整数 ID,就只能用 Number(),并且要清楚超过 Number.MAX_SAFE_INTEGER 的部分会丢精度。
Number()不传参数时Number()什么都不传返回 0,而Number(undefined)和+undefined都是NaN。这个差别来自函数本身对「参数个数为 0」的特殊处理,和转换规则无关,平时很少遇到,但面试偶尔会问。
什么时候用哪一个
一元加号常见于追求简短的地方:+new Date() 拿时间戳,list.sort((a, b) => +a.price - +b.price),或者把 URL 参数里的 '3' 转成 3。这些场景用 + 没问题,但在多人维护的业务代码里,Number(value) 读起来一目了然,也能避开 a + +b 这种需要停下来想一想的写法。
- 和
parseInt、parseFloat的分工 +'12px'是NaN,parseInt('12px', 10)是 12。前者要求整个字符串都是数字,后者从头读到第一个非法字符为止。所以处理 CSS 值、带单位的字符串用parseFloat;校验用户输入是否是合法数字,用Number()加Number.isNaN判断更严格。另外parseInt('')是NaN,而Number('')是 0,空输入的处理结果正好相反。
容易答错的地方
- 以为
+''和Number('')结果不同 - 两者都是 0。真正和它们不同的是
parseInt(''),结果是NaN。把空字符串当成「用户没填」的场景,要先判断value.trim() === '',不能靠转换结果判断。 - 把
Number(x)和new Number(x)混为一谈 - 不带
new调用是类型转换,返回原始数字。带new会创建包装对象,new Number(0)在if里是真值,new Number(1) === 1为假。实际代码里几乎没有理由使用new Number。 - 以为一元加号什么都能转
- 除了 BigInt,
+Symbol()和Number(Symbol())也都会抛TypeError,Symbol 不能被隐式或显式地转成数字。
面试官还会怎么问?
+[]、+{}、+[1, 2] 分别是什么?
分别是 0、NaN、NaN。数组转原始值时调用 toString,[] 是空串,所以是 0;[1, 2] 是 '1,2',不是合法数字;普通对象是 '[object Object]',也是 NaN。
'3' - 1 和 '3' + 1 为什么结果不一样?
减号只有数学含义,会把两边都转成数字,得到 2。加号在任一边是字符串时做拼接,得到 '31'。想让加号做加法,可以写 +'3' + 1 或 Number('3') + 1。
怎么判断一个字符串是不是合法数字?
Number(str) 转完后用 Number.isNaN 判断,同时单独排除空串和纯空白的情况,因为它们会被转成 0。如果还要求是有限数,再加 Number.isFinite,这样 'Infinity' 也会被挡掉。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。