创见博客
声明一个数字有几种写法:字面量、Number() 和 new Number()
七崽爱吃小饼干2026/10/11阅读 0

声明一个数字,有三种写法,看起来差不多,行为却差很多:

js
let a = 1            // 字面量,原始值
let b = Number(1)    // 转成原始值
let c = new Number(1) // 造一个对象

typeof a // "number"
typeof b // "number"
typeof c // "object"   ← 已经不是一个数字了

a === 1 // true
b === 1 // true
c === 1 // false   ← 对象和原始值永远不严格相等

很多人第一次看到 c === 1 为 false 都会愣一下:明明打印出来是 1,怎么就不相等了?

这篇文章就把这三种写法彻底拆开:字面量、Number() 函数、new Number() 构造函数,各自产出什么、能调什么方法、什么时候会踩坑,最后给一张选型表。

一、三者到底各自产出了什么

先记住一句话:

  • 字面量 1:产出原始值(primitive),类型是 "number"。
  • Number(1):是一个普通函数调用(注意前面没有 new),做类型转换,产出原始值。
  • new Number(1):用构造函数造一个包装对象(wrapper object),类型是 "object"。
js
let a = 1
let b = Number(1)
let c = new Number(1)

typeof a          // "number"
typeof b          // "number"
typeof c          // "object"

c instanceof Number // true,它是 Number 的实例
a instanceof Number // false,原始值不是实例

new 是关键分水岭。带 new 的 Number 走的是构造调用,返回一个对象;不带 new 的 Number 走普通调用,返回原始值。同一个标识符,两种身份。

二、三个最直接的差异

2.1 类型:typeof 就不同

js
typeof 1            // "number"
typeof Number(1)    // "number"
typeof new Number(1) // "object"

所以任何用 typeof x === "number" 做判断的地方,new Number(1) 都会漏掉。这是包装对象最典型的坑。

2.2 严格相等:对象和原始值永远不相等

js
1 === 1              // true
Number(1) === 1      // true
new Number(1) === 1  // false,object vs number

new Number(1) === new Number(1) // false,两个不同的对象引用

但宽松相等会隐式拆箱,结果又变成 true:

js
new Number(1) == 1 // true,比较前会调用 valueOf() 取出原始值

这就是 == 的坑之一:对象先转原始值再比。想彻底搞清 == 和 ===,可以看另一篇讲相等运算符的文章。

2.3 真假值:对象恒为真

js
new Number(0) ? '真' : '假' // "真",对象永远是 truthy
Number(0)   ? '真' : '假'   // "假"
0           ? '真' : '假'   // "假"

new Number(0) 在 if 里是真。因为布尔转换只看"是不是对象",不看里面包了什么值。这也是包装对象最容易埋雷的地方。

三、Number() 的本来职责:类型转换

Number() 最常见的用法根本不是"声明数字",而是把别的类型转成数字:

js
Number('42')     // 42
Number('  42 ')  // 42,会修剪首尾空白
Number('')       // 0     ← 空字符串转成 0
Number('42px')   // NaN   ← 只要有一个字符不是数字就是 NaN

Number(true)     // 1
Number(false)    // 0
Number(null)     // 0
Number(undefined) // NaN

Number([])       // 0,[] → '' → 0
Number([5])      // 5,[5] → '5' → 5
Number([1, 2])   // NaN,[1,2] → '1,2' → NaN

记住两条规则就够推:

  1. Number(x) 内部先对 x 做 ToPrimitive,把对象(含数组)转成原始值。
  2. 再把原始值转数字:'' → 0,其他不能解析的 → NaN。

如果只想要整数、还允许遇到非数字字符就停下,用 parseInt:

js
Number('42px')     // NaN
parseInt('42px')   // 42,遇到 p 停止解析
parseInt('ff', 16) // 255,指定进制

日常最省事的转换其实是一元加号 +x,它和 Number(x) 完全等价:

js
+'42'  // 42
+true  // 1
+[]    // 0

四、new Number() 到底造了个什么

new Number(1) 造出来的对象,内部有一个不可直接访问的内部槽 [[NumberData]] 保存着原始值 1。它就是靠这个槽把原始值"包"起来的。

因为它继承自 Number.prototype,所以能调用 Number 的方法:

js
let c = new Number(1.2345)
c.toFixed(2)       // "1.23"
c.toExponential()  // "1.2345e+0"
c.toPrecision(3)   // "1.23"
c.toString()       // "1.2345"
c.valueOf()        // 1.2345,取回原始值

valueOf() 就是取出那个内部槽里的原始值,这也是前面 new Number(1) == 1 能成立的原因——比较时隐式调用了它。

五、关键:原始值也能调这些方法

看到上一节你可能会想:"那包装对象至少多个优点,能调 toFixed 吧?"

并不是。原始值一样能调:

js
(1.2345).toFixed(2)  // "1.23"
1.2345.toFixed(2)    // "1.23"

原理是自动装箱(auto-boxing):访问 1.2345.toFixed 时,引擎临时把原始值 1 包成一个 Number 对象,调用方法,用完立刻丢弃。所以你写 1..toFixed(2) 时得到的临时对象,和 new Number(1) 是同一种东西,区别只是后者被你留下来了。

换句话说,new Number() 能做的事,Number() 和字面量都能做。它没有提供任何额外能力。

那它唯一的"本事"是什么

给对象挂自定义属性——这一点原始值做不到:

js
let n = 1
n.foo = 1 // 静默失败(非严格模式),严格模式抛 TypeError
console.log(n.foo) // undefined

let c = new Number(1)
c.foo = 1 // ✅ 可以
console.log(c.foo) // 1

原理还是自动装箱:n.foo = 1 时临时包了个对象、挂上属性,然后对象立刻被丢弃,所以下一个 n.foo 又是新的临时对象,读不到。而 new Number(1) 是这个对象被真正保留了下来。

但你几乎不会需要一个"能挂属性的数字对象"。所以这点能力在实际开发里也没价值。

六、为什么会有 new Number()

答案不是为了解决什么问题,而是语言设计的历史一致性。

早期 JS 模仿 Java 的"万物皆对象",给每个原始类型都配了对应的包装对象构造函数:Number、String、Boolean。它们对应的写法就是 new Number()、new String()、new Boolean()。

但这套设计从第一天起就弊大于利,只留下了三个坑:

  1. typeof 会变成 "object",所有类型判断失效。
  2. new Number(0) 在条件里是 true,真假判断全乱。
  3. === 永远比不过原始值。

所以现代 JS 里,包装对象构造函数一律不要用 new 调用。需要转换就用 Number(x) 或 +x。

七、一张表选对写法

写法结果类型typeof典型用途
1(字面量)原始值"number"直接写数字,99% 的场景
Number(x)原始值"number"显式类型转换
+x原始值"number"同上,更简洁
parseInt(x) / parseFloat(x)原始值"number"解析可带非数字字符的字符串
new Number(x)包装对象"object"❌ 基本没有正当用途

小结

  • 字面量 1 和 Number(1) 都产出原始值,类型是 "number";new Number(1) 产出对象,类型是 "object"。
  • new Number(1) === 1 为 false;typeof 是 "object";new Number(0) 在 if 里为 true——这三个坑都来自"它是对象"。
  • Number(x) 的真正身份是类型转换工具:Number('') 得 0,Number('42px') 得 NaN;解析带杂字符的字符串用 parseInt。
  • 包装对象能调的 toFixed、valueOf 等方法,原始值靠自动装箱一样能调,所以 new Number() 没有额外能力。
  • 需要转换就用 Number(x) 或 +x,需要原始值就写字面量。new Number() 永远不要用。
评论
0/100