3. 对象的用法
约 2277 字大约 8 分钟
2025-05-15
对象是 JavaScript 最基本的数据结构。TypeScript 对于对象类型有很多规则。
一、简介
1. 声明方式
对象类型的最简单声明方法,就是使用大括号表示对象,在大括号内部声明每个属性和方法的类型。
const obj: {
x: number;
y: number;
} = { x: 1, y: 1 };
属性的类型可以用分号结尾,也可以用逗号结尾。也可以省略分号和逗号,使用换行来代替。
// 此处是定义对象的类型别名
type User1 = { name: string; age: number }; // 分号结尾
type User2 = { name: string; age: number }; // 逗号结尾
type User3 = {
name: string;
age: number;
}; // 换行
TypeScript 可以用 type
、interface
和 class
来定义对象的类型。
// 此处是定义对象的类型别名
type User1 = { name: string; age: number };
// 此处是定义对象的接口
interface User2 {
name: string;
age: number;
}
// 此处是定义对象的类
class User3 {
name: string = "1";
age: number = 1;
}
let user1: User1 = { name: "1", age: 1 };
let user2: User2 = { name: "1", age: 1 };
let user3: User3 = new User3();
2. 严格类型检查
对象的类型检查是严格的。因为对象的类型不可以被改变,所以一旦声明了类型,在给对象赋值时,指定的属性不能多也不能少,同时值的类型需要和类型约束严格匹配。
let user: {
name: string;
age: number;
} = {
name: "Alice",
age: 30,
};
user.age = "20";Type 'string' is not assignable to type 'number'.
user.sex = "male";Property 'sex' does not exist on type '{ name: string; age: number; }'.
3、可选属性
对象的属性可以是可选的,即属性值可以不存在。
let user: {
name: string;
age?: number;
} = {
name: "Alice",
};
4、只读属性
对象的属性可以是只读的,即属性值不能被修改。
let user: {
readonly name: string;
age: number;
} = {
name: "Alice",
age: 30,
};
user.name = "Bob";Cannot assign to 'name' because it is a read-only property.
5、属性索引签名
属性索引签名是指在定义对象类型时,可以使用索引签名来定义对象的属性。
其形式为:
[key: key_type]: value_type
key
是属性索引的索引,可以是任意字符串,但一般使用key
。key_type
是属性索引的属性类型,可以是 string、number、symbol 中的一种。value_type
是属性索引对应的值的类型。
例如:
type User = {
[key1: string]: any; // 索引签名
[key2: number]: any; // 索引签名
};
const user: User = {
id: 1,
name: "Alice",
age: 30,
1: "one",
2: 2,
3: false,
};
二、其他特性
1、对象解构
解构赋值用于直接从对象中提取属性。
const { id, name, price } = product;
解构赋值的类型写法,跟为对象声明类型是一样的。
const {
id,
name,
price,
}: {
id: string;
name: string;
price: number;
} = product;
注意,目前没法为解构变量指定类型,因为对象解构里面的冒号,JavaScript 指定了其他用途:变量别名。
let { x: foo, y: bar } = obj;
// 等同于
let foo = obj.x;
let bar = obj.y;
上面示例中,冒号不是表示属性 x
和 y
的类型,而是为这两个属性指定新的变量名。如果要为 x
和 y
指定类型,不得不写成下面这样。
let { x: foo, y: bar }: { x: string; y: number } = obj;
这一点要特别小心,TypeScript 里面很容易搞糊涂。
function draw({ shape: Shape, xPos: number = 100 }) {Binding element 'Shape' implicitly has an 'any' type. let myShape = shape; // 报错Cannot find name 'shape'. Did you mean 'Shape'? let x = xPos; // 报错Cannot find name 'xPos'.}
上述例子中,Shape
和 number
都被认为是解构赋值的变量别名,而不是变量的类型。
2、自动类型推断
当我们省略类型定义时,TypeScript 自动推断对象的类型,但是尽量不要这样做。
const user = {
name: "Alice", // (property) name: string
age: 30, // (property) age: number
isAdult: true, // (property) isAdult: boolean
};
3、结构类型原则
在 TypeScript 中,只要对象 B 满足 对象 A 的结构特征,TypeScript 就认为对象 B 兼容对象 A 的类型,这称为“结构类型”原则(structural typing)。
根据“结构类型”原则,TypeScript 检查某个值是否符合指定类型时,并不是检查这个值的类型名(即“名义类型”),而是检查这个值的结构是否符合“结构类型”定义 。 例如以下代码:
type A = {
x: number;
};
type B = {
x: number;
y: number;
};
let b: B = {
x: 2,
y: 3,
};
let a: A = b;
console.log(a);
{ x: 2 }
上面示例中,对象A
只有一个属性x
,类型为number
。对象 B 满足这个特征,因此兼容对象A
,只要可以使用A
的地方,就可以使用B
去赋值。但是结果只会按照A
的结构类型保留属性。
在 TypeScript 中,如果B
兼容 A
,那么B
可以被赋值给A
,同时也可以称呼 B
是 A
的子类型。
4、as const
断言
当我们使用as const
断言时,TypeScript 会将对象的属性值转换为字面量类型,间接相当于只读类型。
const user = {
name: "Alice",
age: 30,
isAdult: true,
} as const;
相当于
const user = {
name: "Alice",
age: 30,
isAdult: true,
};
但是我们需要注意一点,下面这种情况可能往往引起我们的误解:
const user: {
name: string;
age: number;
isAdult: boolean;
} = {
name: "Alice",
age: 30,
isAdult: true,
} as const;
user.name = "Bob"; // name 属性的值被修改了。
为什么我们将 user
设置为常量后,还可以修改 name
属性的值呢?
基于结构类型原则,我们将前边的代码中的 =
左右拆解为两部分,再来看:
// 等号右侧
let person = {
name: "Alice",
age: 30,
isAdult: true,
extProp: "1", // 添加一个额外属性辅助理解
} as const;
// 等号左侧
const user: {
name: string;
age: number;
isAdult: boolean;
} = person;
user.name = "Bob"; // name 属性的值被修改了。
user.extProp; // user的类型中没有extProp属性,所以 即便 person 里有,user也不会有。Property 'extProp' does not exist on type '{ name: string; age: number; isAdult: boolean; }'.
as const
修饰的其实是 =
右侧的对象 person
, 而不是 user
,所以user
不是只读对象。将 person
赋值给 user
后,user
对象依旧保持之前的结构类型,其属性值是可以修改的。
如果我们想获取真正的只读对象, 可以使用 readonly
修饰符。
const user: {
readonly name: string;
readonly age: number;
readonly isAdult: boolean;
} = {
name: "Alice",
age: 30,
isAdult: true,
} as const;
user.name = "Bob"; // 报错。Cannot assign to 'name' because it is a read-only property.
5、最小可选属性规则
在 TypeScript 中,对象的可选属性是指在定义对象类型时,可以选择是否给属性赋值。可选属性使用 ?
来表示。但是如果一个对象所有的属性都是可选的,那么这个对象就是一个空对象,即没有任何属性的对象。也就意味着任何对象都是这个空对象的子类型。
为了避免这种情况,TypeScript 2.4 引入了一个“最小可选属性规则”,也称为“弱类型检测”(weak type detection)。
type User = {
name?: string;
age?: number;
};
const Sex = { sex: "male" };
const john: User = Sex; // 报错Type '{ sex: string; }' has no properties in common with type 'User'.
上面示例中,对象 Sex 与类型 User 没有共同属性,赋值给该类型的变量就会报错。
报错原因是,如果某个类型的所有属性都是可选的,那么该类型的对象必须至少存在一个可选属性,不能所有可选属性都不存在。这就叫做“最小可选属性规则”。
如果想规避这条规则,要么在类型里面增加一条索引属性([propName: string]: someType
),要么使用类型断言(Sex as User
)强制转化类型。
6、空对象
空对象是一个没有任何属性的对象,即没有任何属性的对象。空对象的类型是 {}
。
在 JavaScript 中,我们创建对象一般是先创建一个空对象,然后再逐步添加我们需要的属性。而 TypeScript 不允许我们动态修改对象的属性,需要我们在创建对象时就指定所有的属性。
let user = {};
user.name = "Alice"; // Error: 类型“{}”上不存在属性“name”。ts(2339)
这是因为user
的类型被严格推断为{}
,即没有任何属性的对象。我们尝试在添加属性的时候,就会报错。
如果我们想合并属性,那么需要将合并后的属性类型赋值给一个新类型的对象:
let info1 = { name: "Alice" };
let info2 = { age: 30 };
let info3 = { isAdult: true };
let user_info1 = { ...info1, ...info2, ...info3 };
// 或者
let user_info2 = Object.assign({}, info1, info2, info3);
三、其他用法
1、类型别名
对象还有一个很大的用途,就是定义类型别名。主要一下几种用法:
- 定义对象类型
- 定义数组类型
- 定义函数类型
- 定义构造函数类型
- 作为对象类型被类继承
- 作为对象类型被接口实现
用作类型别名时,可以被接口继承,被类实现。
其他更具体的用法可以参考:type 的用法
type User = {
name: string;
age: number;
};
let alice: User = {
name: "Alice",
age: 30,
};
let bob: User = {
name: "bob",
age: 31,
};
2、 被接口继承
interface
可以继承 type
命令定义的对象类型。只能包含属性的定义。
type Country = {
name: string;
capital: string;
};
interface CountryWithPop extends Country {
population: number;
}
3、 被类实现
被类实现的逻辑、类似与接口,可以参考接口的用法
type Country = {
name: string;
capital: string;
};
class MyCountry implements Country {
name = "";
capital = "";
}
let country = new MyCountry();
更新日志
e7112
-1于