什么是 JavaScript 中的包装类型?
·
JavaScript 包装类型详解
一、什么是包装类型
JavaScript 中有 5 种基本类型(原始值):
string、number、boolean、null、undefined
其中 string、number、boolean 各自对应一个引用类型的"包装对象":
| 原始值 | 包装类型 |
|---|---|
string |
String |
number |
Number |
boolean |
Boolean |
包装类型的存在目的:让原始值也能调用属性和方法。
JavaScript 引擎在需要时会自动创建一个临时的包装对象,用完即销毁。这个过程对开发者透明,称为自动装箱(Auto-Boxing)。
二、自动装箱的过程
当你对原始值调用属性或方法时,引擎做了三件事:
let str = "hello";
str.length; // 5
str.toUpperCase(); // "HELLO"
引擎实际执行的是:
第一步:创建一个 String 包装对象
temp = new String("hello") // { 0:"h", 1:"e", 2:"l", 3:"l", 4:"o", length:5 }
第二步:在这个对象上调用属性/方法
temp.length → 5
temp.toUpperCase() → "HELLO"
第三步:销毁这个临时对象
temp = null
三个原始类型都能自动装箱:
// string
"hello".length; // 5
"hello".charAt(0); // "h"
"hello".includes("ell"); // true
// number
(42).toFixed(2); // "42.00"
(3.14).toString(); // "3.14"
Number.isNaN(NaN); // true ← 注意:这是构造函数的静态方法,不是实例方法
// boolean
true.toString(); // "true"
(false).valueOf(); // false
三、自动拆箱的过程
包装对象在需要原始值的上下文中会自动转回原始值:
// 1. 数学运算
let obj = new Number(42);
obj + 8; // 50 ← obj 自动拆箱为 42
// 2. 比较运算
let str = new String("hello");
str == "hello"; // true ← == 触发拆箱比较
str === "hello"; // false ← === 不触发拆箱,类型不同
// 3. 字符串拼接
let num = new Number(100);
"count: " + num; // "count: 100"
拆箱调用的是 valueOf() 或 toString() 方法:
内部行为(ToPrimitive 抽象操作):
1. 优先调用 valueOf(),如果返回原始值则使用
2. 否则调用 toString(),如果返回原始值则使用
3. 否则抛出 TypeError
// valueOf() 返回原始值
new Number(42).valueOf(); // 42
new String("hi").valueOf(); // "hi"
new Boolean(true).valueOf(); // true
// toString() 返回字符串表示
new Number(42).toString(); // "42"
new String("hi").toString(); // "hi"
new Boolean(true).toString(); // "true"
四、一个关键特性:自动创建的包装对象是临时的
let str = "hello";
// 自动装箱创建的临时对象
str.test = "world"; // 1. 创建 new String("hello")
// 2. 设置 test 属性为 "world"
// 3. 销毁临时对象
str.test; // undefined ← 再次访问时是新的临时对象,没有 test 属性
过程解析:
str.test = "world"
└→ temp = new String("hello")
temp.test = "world"
temp 被销毁
str.test
└→ temp = new String("hello") ← 全新的对象
temp.test ← undefined
temp 被销毁
核心结论:自动装箱产生的包装对象是一次性的,无法在其上持久化属性。
五、手动创建 vs 自动装箱的区别
// 自动装箱(原始值)—— 引擎临时创建,用完销毁
let a = "hello";
typeof a; // "string"
a instanceof String; // false
// 手动 new(包装对象)—— 开发者显式创建,持久存在
let b = new String("hello");
typeof b; // "object"
b instanceof String; // true
两者核心差异:
| 方面 | 原始值 "hello" |
包装对象 new String("hello") |
|---|---|---|
typeof |
"string" |
"object" |
instanceof String |
false |
true |
| 能否添加属性 | 不能(临时对象用完即销) | 能(持久对象) |
=== 比较 |
值相同则 true |
永远不同对象 |
| 性能 | 更快 | 有对象创建开销 |
| 推荐使用 | 推荐 | 不推荐 |
// === 比较
"hello" === "hello"; // true ← 原始值比较
new String("hello") === new String("hello"); // false ← 不同对象引用
"hello" === new String("hello"); // false ← 类型不同
六、不推荐手动 new 的原因
1. 类型不一致导致隐式 Bug
// if 判断的陷阱
let a = new Boolean(false);
if (a) {
console.log("会执行!"); // a 是对象,永远为 truthy
}
// new Boolean(false) 的 typeof 是 "object",对象 → truthy
// 原始值则正常
let b = false;
if (b) {
console.log("不会执行"); // false → falsy
}
2. typeof 和 instanceof 行为混乱
typeof false; // "boolean" ✓
typeof new Boolean(false); // "object" ✗
typeof 42; // "number"
typeof new Number(42); // "object"
typeof "hello"; // "string"
typeof new String("hello"); // "object"
3. 比较行为不可预期
let x = new Number(1);
let y = new Number(1);
x == y; // true ← == 触发拆箱,比较值
x === y; // false === 比较引用,不同对象
x + y; // 2 ← 运算时拆箱
[x]; // [Number{1}] ← 数组中不拆箱
结论:在代码中永远不要手动 new String()、new Number()、new Boolean()。
// 正确做法:直接使用原始值和字面量
let name = "hello"; // ✓
let count = 42; // ✓
let flag = true; // ✓
// 错误做法:手动创建包装对象
let name = new String("hello"); // ✗
let count = new Number(42); // ✗
let flag = new Boolean(true); // ✗
七、包装类型的实际用途
既然不推荐 new,那包装类型有什么用?
用途一:调用静态方法
// Number 的静态方法
Number.parseInt("42"); // 42
Number.parseFloat("3.14"); // 3.14
Number.isFinite(42); // true
Number.isNaN(NaN); // true
Number.isInteger(42); // true
Number.MAX_SAFE_INTEGER; // 9007199254740991
// String 的静态方法
String.fromCharCode(65); // "A"
String.fromCodePoint(0x1F600); // "😀"
// Boolean 的静态方法
Boolean(true); // true
Boolean(0); // false
Boolean(""); // false
Boolean(null); // false
Boolean({}); // true ← 对象永远 true
用途二:显式类型转换
// 当作函数调用(不带 new)进行类型转换
Number("42"); // 42 ← 字符串 → 数字
Number(""); // 0
Number("hello"); // NaN
Number(null); // 0
Number(undefined); // NaN
Number(true); // 1
String(42); // "42" ← 数字 → 字符串
String(true); // "true"
String(null); // "null"
String([1,2,3]); // "1,2,3"
Boolean(0); // false ← Falsy 值
Boolean(""); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
Boolean(1); // true ← Truthy 值
Boolean("hello"); // true
Boolean({}); // true
不带 new 调用时,它们是类型转换函数,不是构造函数——这是推荐的用法。
用途三:借用方法
// 借用数组的方法处理字符串
Array.prototype.slice.call("hello"); // ["h", "e", "l", "l", "o"]
// 借用包装对象的原型方法
let str = "hello";
String.prototype.toUpperCase.call(str); // "HELLO"
八、原型链上的方法来自哪里
let str = "hello";
str.toUpperCase();
// 为什么原始值能调用 toUpperCase?
// 原因:原始值在需要时,会沿着原型链查找
// str 的实际原型链:
"hello"(临时 new String("hello"))
└→ String.prototype
└→ Object.prototype
└→ null
// toUpperCase 定义在 String.prototype 上
完整原型链关系:
原始值 "hello"
│
│ 自动装箱 → new String("hello")
│
▼
String.prototype ← toUpperCase、charAt、slice、trim 等方法
│
▼
Object.prototype ← toString、valueOf、hasOwnProperty 等方法
│
▼
null
同理,number 和 boolean 也有对应的原型链:
// Number.prototype 上的方法
(42).toFixed(2); // "42.00"
(3.14).toPrecision(3); // "3.14"
// Boolean.prototype 上的方法
(true).toString(); // "true"
九、Symbol 和 BigInt 的包装类型
ES6 新增的两种原始类型也有对应包装类型:
// Symbol
let s = Symbol("test");
typeof s; // "symbol"
s.description; // "test"
// BigInt
let n = 42n;
typeof n; // "bigint"
n.toString(); // "42"
// 对应的包装构造函数
Symbol("test") instanceof Symbol; // true(但 Symbol 不能 new,只能函数调用)
BigInt(42) instanceof BigInt; // true(BigInt 不能 new)
// Symbol 不能用 new
new Symbol("test"); // TypeError: Symbol is not a constructor
// BigInt 不能用 new
new BigInt(42); // TypeError: BigInt is not a constructor
ES6 的改进:Symbol 和 BigInt 禁止 new 调用,强制开发者只使用原始值,避免了包装对象带来的歧义。
十、null 和 undefined 没有包装类型
null.toString(); // TypeError
undefined.toString(); // TypeError
// null 和 undefined 没有对应包装类型
// 没有方法、没有属性,是纯粹的"空值"
完整的原始值与包装类型对应关系:
| 原始值 | 包装类型 | 有包装? |
|---|---|---|
string |
String |
有 |
number |
Number |
有 |
boolean |
Boolean |
有 |
symbol |
Symbol |
有(禁止 new) |
bigint |
BigInt |
有(禁止 new) |
null |
无 | 无 |
undefined |
无 | 无 |
十一、总结
JavaScript 的包装类型是引擎为了"让原始值也能调用方法"而设计的桥接机制。 当你对
"hello".length、(42).toFixed()调用方法时,引擎在幕后创建了一个临时的String/Number对象,方法执行完立即销毁。
要点 结论 自动装箱 引擎透明完成,开发者无需关心 自动拆箱 运算/比较时自动调用 valueOf()临时性 自动创建的对象用完即销毁,无法持久属性 手动 new永远不要用,类型不可预期 不带 new调用推荐,当作类型转换函数使用 静态方法 包装类型的实际价值所在( Number.isNaN等)
更多推荐

所有评论(0)