javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > TypeScript 装箱与拆箱

TypeScript中装箱与拆箱类型

作者:小赵同学WoW

本文主要介绍了TypeScript中装箱与拆箱类型,用通俗例子讲清原始类型与包装对象的区别,包括装箱拆箱机制、赋值关系,以及object、Object和{}的差异,帮你避开类型标注的常见坑

1. 原始类型与包装对象类型

TypeScript 中常见的原始类型都小写:

string
number
boolean
symbol
bigint

JavaScript 还为它们提供了对应的包装对象构造函数:

原始类型(推荐)包装对象类型(不推荐)创建包装对象
stringStringnew String('hello')
numberNumbernew Number(123)
booleanBooleannew Boolean(true)
symbolSymbol不能使用 new Symbol()
bigintBigInt不能使用 new BigInt()

小写类型表示日常使用的原始值,大写类型表示包装对象具有的类型。

const primitive: string = 'hello';
const boxed: String = new String('hello');

typeof primitive; // 'string'
typeof boxed;     // 'object'

声明变量、函数参数和返回值时,应使用 string、number、boolean 等小写原始类型,不要使用 String、Number、Boolean 等大写包装对象类型。

2. 什么是装箱

装箱是把原始值临时包装成对象,使它能够访问对应包装对象原型上的属性和方法。

const message = 'hello';

message.toUpperCase(); // 'HELLO'
message.length;        // 5

message 明明是原始字符串,却能调用 String.prototype.toUpperCase。可以把 JavaScript 在背后做的事情近似理解成:

new String(message).toUpperCase();

这个包装对象通常只是临时产生的,方法调用结束后就会被丢弃。开发时不需要、也不应该手动创建它。

临时包装对象不会保存新增属性

const message = 'hello';

const wrapper1 = Object(message) as String & { language?: string };
wrapper1.language = 'en';

const wrapper2 = Object(message) as String & { language?: string };
console.log(wrapper2.language); // undefined

两次包装产生的不是同一个对象,所以新增属性无法保留。直接给原始值添加属性也不可靠,在严格模式下还可能抛出异常。

3. 什么是拆箱

拆箱是从包装对象中取出其原始值。JavaScript 通常会在需要原始值时自动完成转换,也可以显式调用 valueOf():

const boxed = new Number(10);
const primitive = boxed.valueOf();

console.log(primitive);        // 10
console.log(typeof primitive); // 'number'

字符串包装对象也是同样的道理:

const boxed = new String('hello');
const primitive = boxed.valueOf();

console.log(typeof boxed);     // 'object'
console.log(typeof primitive); // 'string'

4. TypeScript 中的赋值关系

原始值可以赋给对应的包装对象类型,但包装对象不能赋给原始类型:

let primitive: string = 'hello';
let boxed: String = new String('hello');

boxed = primitive; // ✅ string 可以赋给 String
primitive = boxed; // ❌ String 不能赋给 string

原因是 String 的范围更大,它既可以接收字符串原始值,也可以接收真正的字符串包装对象;而 string 只接受字符串原始值。

可以把兼容关系简单理解为:

字符串字面量类型 ⊆ string ⊆ String

let literal: 'hello' = 'hello';
let primitive: string = literal; // ✅
let boxed: String = primitive;   // ✅

// literal = primitive; // ❌ string 的范围比 'hello' 更大
// primitive = boxed;   // ❌ String 可能是真正的包装对象

5. 为什么不推荐包装对象

5.1 类型容易变宽

使用 String 意味着调用者可以传入 new String() 创建的对象,这通常不是我们真正想要的数据:

function printName(name: String) {
  console.log(name);
}

printName('小明');
printName(new String('小明')); // 也能传入

改成 string 后,函数只接收普通字符串:

function printName(name: string) {
  console.log(name);
}

printName('小明');
// printName(new String('小明')); // ❌

5.2 包装对象的真假值可能违反直觉

对象在条件判断中永远是真值,即使它包装的是 false:

const value = new Boolean(false);

if (value) {
  console.log('会执行');
}

console.log(value.valueOf()); // false

因此,手动创建包装对象不仅没有必要,还容易产生运行时错误。

5.3 原始值与包装对象并不严格相等

const primitive = 'hello';
const boxed = new String('hello');

console.log(primitive === boxed); // false

一个是原始值,一个是对象,它们不是同一种值。

6.object、Object与{}的区别

这三个类型看起来相似,但含义不同。

6.1object

小写 object 表示非原始类型,只接收对象、数组和函数等值:

let value: object;

value = { name: '小明' }; // ✅
value = [1, 2, 3];       // ✅
value = () => {};        // ✅

// value = 'hello'; // ❌
// value = 123;     // ❌

不过,object 只说明这个值不是原始值,并没有描述它有哪些具体属性:

const user: object = { name: '小明' };

// user.name; // ❌ object 类型中没有声明 name

需要访问具体属性时,应该声明准确的对象结构:

const user: { name: string } = { name: '小明' };

console.log(user.name); // ✅

6.2Object

大写 Object 描述所有具有 Object 基本成员的值。在开启 strictNullChecks 时,它可以接收除 null 和 undefined 之外的值:

let value: Object;

value = {};      // ✅
value = 'hello'; // ✅
value = 123;     // ✅
value = true;    // ✅

// value = null;      // ❌ strictNullChecks 开启时
// value = undefined; // ❌ strictNullChecks 开启时

日常业务代码中一般不使用 Object,因为它的范围太宽,无法准确表达数据结构。

6.3{}

作为类型使用时,{} 不是“只能接收空对象”,而是表示任意非空值,即除 null 和 undefined 之外的值:

let value: {};

value = {};      // ✅
value = 'hello'; // ✅
value = 123;     // ✅
value = true;    // ✅

// value = null;      // ❌ strictNullChecks 开启时
// value = undefined; // ❌ strictNullChecks 开启时

如果想表示“一个暂时不知道具体属性的对象”,可以使用 object;如果想表示一个键值对象,可以根据需要使用 Record<string, unknown>:

let objectValue: object = {};
let recordValue: Record<string, unknown> = {};

6.4 对比总结

在开启 strictNullChecks 时:

值object{}Object
{}✅✅✅
[]✅✅✅
() => {}✅✅✅
new String('hello')✅✅✅
'hello'❌✅✅
123❌✅✅
true❌✅✅
null❌❌❌
undefined❌❌❌

若关闭 strictNullChecks,null 和 undefined 的赋值规则会变宽。实际项目建议始终开启严格模式。

7. 容易混淆的两个概念

String()与new String()不一样

不使用 new 时,String() 是普通的类型转换函数,返回原始字符串:

const value1 = String(123);
const value2 = new String(123);

console.log(typeof value1); // 'string'
console.log(typeof value2); // 'object'

所以 String(value) 可以用于显式转换,但 new String(value) 不推荐使用。

TypeScript 类型在运行时不存在

let name: string = '小明';

这里的 string 只用于编译阶段的类型检查,编译后会被删除。真正执行装箱和拆箱的是 JavaScript,而不是 TypeScript 的类型系统。

8. 实际开发原则

  1. 使用小写原始类型:string、number、boolean、symbol、bigint。
  2. 不要把 String、Number、Boolean 等包装对象类型用于类型标注。
  3. 不要使用 new String()、new Number()、new Boolean() 创建包装对象。
  4. 表示非原始值时使用 object,需要读取属性时声明具体对象结构。
  5. 不要把 {} 当作“空对象类型”,它实际表示任意非 null、非 undefined 的值。
  6. 尽量不用大写 Object,它的范围过宽,通常无法准确表达意图。

9. 一句话总结

原始值在访问属性或方法时,JavaScript 会临时将它装箱为包装对象;需要原始值时又会拆箱。TypeScript 允许原始类型赋给对应的包装对象类型,但开发中应始终优先使用小写原始类型,避免手动创建和标注包装对象。

到此这篇关于TypeScript中装箱与拆箱类型的文章就介绍到这了,更多相关TypeScript 装箱与拆箱内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文