【阮一峰】6.对象
- IT业界
- 2025-08-30 05:24:01

对象 简介
对象的类型就写在变量名后面,使用大括号描述,内部声明每个属性的属性名和类型。
const obj: { x: number; y: number; } = { x: 1, y: 1 };属性的类型可以用分号结尾,也可以用逗号结尾。
// 属性类型以分号结尾 type MyObj = { x: number; y: number; }; // 属性类型以逗号结尾 type MyObj = { x: number; y: number; };一旦声明了类型,对象赋值时,就不能缺少指定的属性,也不能有多余的属性。
对象的方法使用函数类型描述。
const obj: { x: number; y: number; add(x: number, y: number): number; // 或者写成 // add: (x:number, y:number) => number; } = { x: 1, y: 1, add(x, y) { return x + y; }, };对象类型可以使用方括号读取属性的类型。
type User = { name: string; age: number; }; type Name = User["name"]; // string除了type命令可以为对象类型声明一个别名,interface命令可以把对象类型提炼为一个接口。
// 写法一 type MyObj = { x: number; y: number; }; const obj: MyObj = { x: 1, y: 1 }; // 写法二 interface MyObj { x: number; y: number; } const obj: MyObj = { x: 1, y: 1 };:::tip TypeScript 不区分对象自身的属性和继承的属性,一律视为对象的属性。 :::
interface MyInterface { toString(): string; // 继承的属性 prop: number; // 自身的属性 } const obj: MyInterface = { // 正确 prop: 123, };obj只声明了prop属性,但是不报错,因为它继承了原型上的toString()方法。
可选属性可选属性在属性名后面加一个问号。
const obj: { x: number; y?: number; } = { x: 1 }; 只读属性属性名前面加上 readonly 关键字,表示这个属性是只读属性,不能修改。
interface MyInterface { readonly prop: number; }只读属性只能在对象初始化期间赋值,此后就不能修改该属性。
:::tip 如果属性值是一个对象,readonly 修饰符并不禁止修改该对象的属性,只是禁止完全替换掉该对象。 :::
interface Home { readonly resident: { name: string; age: number; }; } const h: Home = { resident: { name: "Vicky", age: 42, }, }; h.resident.age = 32; // 正确 h.resident = { name: "Kate", age: 23, }; // 报错:::tip 如果一个对象有两个引用,即两个变量对应同一个对象,其中一个变量是可写的,另一个变量是只读的,那么从可写变量修改属性,会影响到只读变量。 :::
interface Person { name: string; age: number; } interface ReadonlyPerson { readonly name: string; readonly age: number; } let w: Person = { name: "Vicky", age: 42, }; let r: ReadonlyPerson = w; w.age += 1; r.age; // 43赋值时在对象后面加上只读断言 as const,也可以声明属性是只读。
const myUser = { name: "Sabrina", } as const; myUser.name = "Cynthia"; // 报错如果变量明确地声明了类型,那么 TypeScript 会以声明的类型为准。
const myUser: { name: string } = { name: "Sabrina", } as const; myUser.name = "Cynthia"; // 正确 属性名的索引类型TypeScript 允许采用属性名表达式的写法来描述类型,称为“属性名的索引类型”。
索引类型里面,最常见的就是属性名的字符串索引。
type MyObj = { [property: string]: string; }; const obj: MyObj = { foo: "a", bar: "b", baz: "c", };对象可以同时有多种类型的属性名索引,比如同时有数值索引和字符串索引。
数值索引不能与字符串索引发生冲突,必须服从后者。因为在 JavaScript 语言内部,所有的数值属性名都会自动转为字符串属性名。
属性名的数值索引不宜用来声明数组,因为采用这种方式声明数组,就不能使用各种数组方法以及 length 属性,因为类型里面没有定义这些东西。
type MyArr = { [n: number]: number; }; const arr: MyArr = [1, 2, 3]; arr.length; // 报错 解构赋值解构赋值的类型写法,跟为对象声明类型是一样的。
const { id, name, price, }: { id: string; name: string; price: number; } = product;:::tip 目前没法为解构变量指定类型,因为对象解构里面的冒号,JavaScript 指定了作为解构变量的别名。 :::
let { x: foo, y: bar } = obj; // 等同于 let foo = obj.x; let bar = obj.y; 结构类型原则只要对象 B 满足 对象 A 的结构特征,TypeScript 就认为对象 B 兼容对象 A 的类型,这称为“结构类型”原则(structural typing)。
type A = { x: number; }; type B = { x: number; y: number; };只要可以使用 A 的地方,就可以使用 B。
:::tip 如果类型 B 可以赋值给类型 A,TypeScript 就认为 B 是 A 的子类型(subtyping),A 是 B 的父类型。子类型满足父类型的所有结构特征,同时还具有自己的特征。凡是可以使用父类型的地方,都可以使用子类型,即子类型兼容父类型。 :::
严格字面量检查如果对象使用字面量表示,会触发 TypeScript 的严格字面量检查(strict object literal checking)。如果字面量的结构跟类型定义的不一样(比如多出了未定义的属性),就会报错。
const point: { x: number; y: number; } = { x: 1, y: 1, z: 1, // 报错 };如果等号右边不是字面量,而是一个变量,根据结构类型原则,是不会报错的。
const myPoint = { x: 1, y: 1, z: 1, }; const point: { x: number; y: number; } = myPoint; // 正确如果允许字面量有多余属性,可以像下面这样在类型里面定义一个通用属性。
let x: { foo: number; [x: string]: any; }; x = { foo: 1, baz: 2 }; // Ok 最小可选属性规则根据“结构类型”原则,如果一个对象的所有属性都是可选的,那么其他对象跟它都是结构类似的。
为了避免这种情况,TypeScript 2.4 引入了一个“最小可选属性规则”,也称为“弱类型检测”(weak type detection)。
如果某个类型的所有属性都是可选的,那么该类型的对象必须至少存在一个可选属性,不能所有可选属性都不存在。这就叫做“最小可选属性规则”。
空对象空对象是 TypeScript 的一种特殊值,也是一种特殊类型。
const obj = {}; obj.prop = 123; // 报错 obj.toString(); // 正确原因是这时 TypeScript 会推断变量 obj 的类型为空对象,实际执行的是下面的代码。
const obj: {} = {};空对象没有自定义属性,所以对自定义属性赋值就会报错。空对象只能使用继承的属性,即继承自原型对象 Object.prototype 的属性。
TypeScript 不允许动态添加属性,所以对象不能分步生成,必须生成时一次性声明所有属性。
如果确实需要分步声明,一个比较好的方法是,使用扩展运算符(…)合成一个新对象。
空对象作为类型,其实是 Object 类型的简写形式。