TypeScript 全面详解:对象类型的语法规则与实战指南

TypeScript 全面详解:对象类型的语法规则与实战指南

🔥全面解析 TypeScript 对象类型的语法细节和使用规范。

一、对象类型的基础声明

1. 直接字面量声明

对象类型最简单的声明方式,就是使用大括号 {} 包裹,内部逐一声明每个属性的名称和对应类型,这是最直观的对象类型定义方式。

typescript 复制代码
// 变量后直接声明对象类型
const obj: {
  x: number;
  y: number;
} = { x: 1, y: 1 };

上述示例中,变量 obj 的类型直接写在变量名后,大括号内明确约束了 xy 两个属性均为 number 类型,赋值时必须严格匹配该类型结构。

2. 属性分隔符规则

对象类型内部的属性之间,支持分号 ;逗号 , 作为分隔符,两种写法效果完全一致,可根据个人或团队编码风格选择。

typescript 复制代码
// 写法 1:分号分隔(推荐,更清晰区分属性与类型结束)
type MyObj1 = {
  x: number;
  y: number;
};

// 写法 2:逗号分隔(贴近 JavaScript 对象字面量写法)
type MyObj2 = {
  x: number,
  y: number,
};

另外,最后一个属性后面的分隔符是可选的,可写可不写,不会影响类型校验。

typescript 复制代码
// 合法:最后一个属性后无分隔符
type MyObj3 = {
  x: number;
  y: number
};

3. 严格的属性匹配规则

一旦声明了对象类型,赋值时就必须严格遵守「不多不少」的原则:不能缺少声明过的属性,也不能额外添加未声明的属性。

typescript 复制代码
type MyObj = {
  x: number;
  y: number;
};

// 错误:缺少属性 y(Property 'y' is missing in type '{ x: number; }')
const o1: MyObj = { x: 1 };

// 错误:多余属性 z(Object literal may only specify known properties)
const o2: MyObj = { x: 1, y: 1, z: 1 };

这种严格校验仅针对「对象字面量直接赋值」,如果是将对象赋值给一个变量后再传递,多余属性不会报错(这是 TS 的「对象字面量新鲜度」规则),但仍不推荐这样做。

4. 属性的读写与删除规则

  • 读写未声明的属性:会直接报错,TS 不允许访问对象类型中不存在的属性。
  • 删除已声明的属性:会报错,TS 不允许删除类型声明中明确存在的属性。
  • 修改已声明属性的值:合法,只要值的类型与声明类型一致即可。
typescript 复制代码
const obj: {
  x: number;
  y: number;
} = { x: 1, y: 1 };

// 错误:读取不存在的属性 z(Property 'z' does not exist on type '{ x: number; y: number; }')
console.log(obj.z);

// 错误:写入不存在的属性 z(Property 'z' does not exist on type '{ x: number; y: number; }')
obj.z = 1;

const myUser: { name: string } = {
  name: "Sabrina",
};

// 错误:删除已声明的属性 name(The operand of a 'delete' operator must be optional)
delete myUser.name;

// 正确:修改属性值(类型匹配)
myUser.name = "Cynthia";

二、对象方法的类型声明

对象中的方法可以通过两种方式声明类型:函数签名语法箭头函数语法,两种写法效果一致,推荐使用函数签名语法(更贴近 JavaScript 方法的书写习惯)。

typescript 复制代码
const obj: {
  x: number;
  y: number;
  // 写法 1:函数签名语法(推荐)
  add(x: number, y: number): number;
  // 写法 2:箭头函数语法
  subtract: (x: number, y: number) => number;
} = {
  x: 1,
  y: 1,
  add(x, y) {
    return x + y;
  },
  subtract(x, y) {
    return x - y;
  }
};

上述示例中,方法 addsubtract 均声明了参数类型(两个 number 类型)和返回值类型(number 类型),实现时必须严格遵循该类型约束。

三、对象属性类型的读取

TypeScript 支持使用方括号 [] 读取对象类型中某个属性的具体类型,这在提取单个属性类型、实现类型复用场景中非常有用。

typescript 复制代码
// 定义完整对象类型
type User = {
  name: string;
  age: number;
  gender: boolean;
};

// 读取 name 属性的类型 → string
type UserName = User['name'];

// 读取 age 属性的类型 → number
type UserAge = User['age'];

// 实际使用:约束变量类型为 User 的 name 属性类型
const userName: UserName = "张三";
const userAge: UserAge = 22;

注意,方括号内的属性名必须用引号包裹(字符串字面量),且必须是对象类型中已声明的属性,否则会报错。

四、对象类型的两种别名方式:type vs interface

除了直接字面量声明,TypeScript 还提供了两种方式将对象类型提炼为可复用的别名:type 命令和 interface 命令,两者在对象类型声明场景中功能相似。

1. 两种方式的基本使用

typescript 复制代码
// 写法 1:type 命令(类型别名)
type MyObjType = {
  x: number;
  y: number;
};

const obj1: MyObjType = { x: 1, y: 1 };

// 写法 2:interface 命令(接口)
interface MyObjInterface {
  x: number;
  y: number;
}

const obj2: MyObjInterface = { x: 1, y: 1 };

2. 接口的特殊特性:继承属性的兼容

TypeScript 不区分对象「自身属性」和「继承属性」,一律视为对象的合法属性。因此,在接口中声明的继承属性(如 toString()),实现时无需手动提供(可从原型链继承)。

typescript 复制代码
interface MyInterface {
  // 继承属性:从 Object 原型链继承
  toString(): string;
  // 自身属性:必须手动提供
  prop: number;
}

// 正确:仅提供自身属性 prop,toString() 从原型链继承
const obj: MyInterface = {
  prop: 123,
};

上述示例中,obj 仅提供了 prop 属性,但仍符合 MyInterface 接口约束,因为 toString() 方法可从 Object 原型链中继承,无需手动实现。

五、可选属性

1. 可选属性的声明:?

如果某个属性不是必需的(可存在、可不存在),可以在属性名后添加问号 ? 标记为可选属性。

typescript 复制代码
// 声明 y 为可选属性
const obj: {
  x: number;
  y?: number;
} = { x: 1 }; // 正确:无需提供 y 属性

2. 可选属性与 undefined 的等价性

可选属性本质上等同于「允许赋值为 undefined 的属性」,下面两种写法完全等效:

typescript 复制代码
// 写法 1:简洁写法
type User1 = {
  firstName: string;
  lastName?: string;
};

// 写法 2:完整写法(明确允许 undefined)
type User2 = {
  firstName: string;
  lastName: string | undefined;
};

因此,可选属性可以显式赋值为 undefined,不会报错:

typescript 复制代码
const obj: {
  x: number;
  y?: number;
} = { x: 1, y: undefined }; // 正确

3. 可选属性的使用注意事项

读取未赋值的可选属性时,返回值为 undefined,直接调用该属性的方法会报错,因此使用前必须先进行 undefined 校验。

typescript 复制代码
type MyObj = {
  x: string;
  y?: string;
};

const obj: MyObj = { x: 'hello' };

// 错误:Cannot read properties of undefined (reading 'toLowerCase')
obj.y.toLowerCase();

常用的校验方式有两种:条件判断或 Null 合并运算符 ??(推荐)。

typescript 复制代码
const user: {
  firstName: string;
  lastName?: string;
} = { firstName: 'Foo' };

// 方式 1:条件判断
if (user.lastName !== undefined) {
  console.log(`hello ${user.firstName} ${user.lastName}`);
}

// 方式 2:Null 合并运算符(设置默认值,更简洁)
const firstName = user.firstName ?? '默认姓名';
const lastName = user.lastName ?? '默认姓氏';

console.log(`hello ${firstName} ${lastName}`);

4. 严格可选属性配置:ExactOptionalPropertyTypes

TypeScript 提供了编译配置 ExactOptionalPropertyTypes,当该配置与 strictNullChecks 同时开启时,可选属性将不允许显式赋值为 undefined,仅允许省略不写。

typescript 复制代码
// 开启 ExactOptionalPropertyTypes 和 strictNullChecks 后
const obj: {
  x: number;
  y?: number;
} = { x: 1, y: undefined }; // 错误:Type 'undefined' is not assignable to type 'number | undefined'

5. 可选属性 vs 允许 undefined 的必选属性

两者看似相似,但存在核心区别:可选属性可省略不写,而允许 undefined 的必选属性必须显式提供(即使值为 undefined)。

typescript 复制代码
type A = { x: number; y?: number }; // y 是可选属性
type B = { x: number; y: number | undefined }; // y 是必选属性(允许 undefined)

const objA: A = { x: 1 }; // 正确:可选属性可省略
const objB: B = { x: 1 }; // 错误:缺少必选属性 y(Property 'y' is missing in type '{ x: number; }')
const objB2: B = { x: 1, y: undefined }; // 正确:显式提供 y 并赋值为 undefined

六、只读属性

1. 只读属性的声明:readonly

在属性名前添加**readonly 关键字**,可将该属性标记为只读属性,初始化赋值后无法再修改其值。

typescript 复制代码
// 接口中声明只读属性
interface MyInterface {
  readonly prop: number;
}

// 直接字面量声明只读属性
const person: {
  readonly age: number;
} = { age: 20 };

// 错误:Cannot assign to 'age' because it is a read-only property
person.age = 21;

只读属性仅能在对象初始化期间赋值,此后任何修改操作都会被 TS 禁止。

typescript 复制代码
type Point = {
  readonly x: number;
  readonly y: number;
};

// 初始化赋值(合法)
const p: Point = { x: 0, y: 0 };

// 后续修改(非法)
p.x = 100; // 错误

2. 只读属性的深层注意事项

readonly 修饰符仅约束「属性本身的赋值」,如果属性值是一个对象(引用类型),readonly 不会禁止修改该对象的内部属性,仅禁止完全替换该对象。

typescript 复制代码
interface Home {
  readonly resident: {
    name: string;
    age: number;
  };
}

const h: Home = {
  resident: {
    name: 'Vicky',
    age: 42
  }
};

// 正确:修改对象内部属性(readonly 不约束深层属性)
h.resident.age = 32;

// 错误:完全替换 readonly 属性的值(Cannot assign to 'resident' because it is a read-only property)
h.resident = {
  name: 'Kate',
  age: 23
};

3. 只读引用的相互影响

如果一个对象有两个引用(一个可写、一个只读),修改可写引用的属性,会影响到只读引用(因为两者指向同一个对象)。

typescript 复制代码
interface Person {
  name: string;
  age: number;
}

interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}

// 可写引用
let writablePerson: Person = {
  name: 'Vicky',
  age: 42,
};

// 只读引用(指向同一个对象)
let readonlyPerson: ReadonlyPerson = writablePerson;

// 修改可写引用的属性
writablePerson.age += 1;

// 只读引用的属性值也会变化(输出 43)
console.log(readonlyPerson.age);

4. 只读断言:as const

除了声明时使用 readonly,还可以在对象赋值时使用**as const 只读断言**,将整个对象转为只读对象,所有属性均无法修改。

typescript 复制代码
const myUser = {
  name: "Sabrina",
} as const;

// 错误:Cannot assign to 'name' because it is a read-only property
myUser.name = "Cynthia";

注意:如果变量已经明确声明了类型,TS 会以「声明的类型」为准,as const 断言会被忽略。

typescript 复制代码
// 明确声明类型:name 为可写的 string 类型
const myUser: { name: string } = {
  name: "Sabrina",
} as const;

// 正确:以声明的类型为准,name 可修改
myUser.name = "Cynthia";

七、属性名的索引类型

当对象的属性名无法提前确定(如 API 返回的动态数据),或属性数量过多时,TypeScript 允许使用「属性名的索引类型」(又称索引签名)来描述对象类型,支持字符串、数字、Symbol 三种索引类型。

1. 字符串索引类型

最常用的索引类型,用于约束「属性名为字符串、属性值为统一类型」的对象。

typescript 复制代码
// 字符串索引类型:属性名是 string,属性值是 string
type StringMap = {
  [property: string]: string;
};

// 合法:所有属性均符合索引类型约束
const language: StringMap = {
  zh: "中文",
  en: "英文",
  ja: "日文",
};

其中,[property: string] 中的 property 是自定义的变量名,可任意修改(如 [key: string]),不影响类型校验。

2. 数字索引类型

用于约束「属性名为数字、属性值为统一类型」的对象,常用来描述类数组对象。

typescript 复制代码
// 数字索引类型:属性名是 number,属性值是 number
type NumberMap = {
  [index: number]: number;
};

// 合法:类数组对象
const arrLike: NumberMap = {
  0: 1,
  1: 2,
  2: 3,
};

// 也可用于简单数组(不推荐,数组有专门的类型声明方式)
const simpleArr: NumberMap = [1, 2, 3];

3. 索引类型的约束规则

  1. 数字索引服从字符串索引:JavaScript 内部会将数字属性名自动转为字符串,因此当对象同时存在数字索引和字符串索引时,数字索引的属性值类型必须兼容字符串索引的属性值类型,否则会报错。
typescript 复制代码
// 错误:数字索引类型(boolean)与字符串索引类型(string)不兼容
type MyType = {
  [x: number]: boolean;
  [x: string]: string;
};

// 正确:数字索引类型(string)兼容字符串索引类型(string)
type MyType2 = {
  [x: number]: string;
  [x: string]: string;
};
  1. 具体属性必须兼容索引类型:如果对象同时声明了具体属性和索引类型,具体属性的类型必须兼容索引类型的属性值类型,否则会报错。
typescript 复制代码
// 错误:具体属性 foo(boolean)与索引类型(string)不兼容
type MyType3 = {
  foo: boolean;
  [x: string]: string;
};

// 正确:具体属性 foo(string)兼容索引类型(string)
type MyType4 = {
  foo: string;
  [x: string]: string;
};

4. 索引类型的使用注意事项

索引类型的约束较为宽泛,容易忽略具体属性的细节校验,建议谨慎使用。另外,数字索引类型不宜用来声明数组,因为这种方式无法支持数组的 length 属性和各类数组方法(如 push()map())。

typescript 复制代码
type MyArr = {
  [n: number]: number;
};

const arr: MyArr = [1, 2, 3];

// 错误:Property 'length' does not exist on type 'MyArr'
console.log(arr.length);

// 错误:Property 'push' does not exist on type 'MyArr'
arr.push(4);

八、对象的解构赋值与类型声明

TypeScript 支持 JavaScript 的对象解构赋值,同时也需要为解构后的变量添加类型约束,其类型写法与对象类型声明一致。

1. 基本解构赋值的类型声明

typescript 复制代码
// 定义对象类型
type Product = {
  id: string;
  name: string;
  price: number;
};

// 定义原始对象
const product: Product = {
  id: "P001",
  name: "TypeScript 实战指南",
  price: 59.9,
};

// 解构赋值并添加类型声明
const { id, name, price }: {
  id: string;
  name: string;
  price: number;
} = product;

// 或直接使用已定义的类型别名(推荐,更简洁)
const { id: productId, name: productName }: Product = product;

2. 解构赋值中的冒号陷阱

需要特别注意:对象解构中的冒号 : 不是用来指定类型的,而是用来为属性重命名的。如果要为解构变量指定类型,必须在解构表达式外部整体添加类型声明。

typescript 复制代码
const obj = { x: "hello", y: 123 };

// 解构重命名:x → foo,y → bar(冒号不是类型声明)
let { x: foo, y: bar } = obj;

// 等同于
let foo = obj.x;
let bar = obj.y;

// 正确:外部添加整体类型声明
let { x: foo2, y: bar2 }: { x: string; y: number } = obj;

3. 函数参数解构的类型声明

在函数参数中使用对象解构时,同样需要注意冒号的用途,避免将重命名误认为类型声明。

typescript 复制代码
// 错误示例:将重命名误认为类型声明
function draw({
  shape: Shape, // 重命名:shape → Shape
  xPos: number = 100 // 错误:无法将类型 'number' 赋值给变量 xPos
}) {
  let myShape = shape; // 错误:找不到变量 shape
  let x = xPos; // 错误:找不到变量 xPos
}

// 正确示例:外部添加类型声明
function drawCorrect({
  shape,
  xPos = 100
}: {
  shape: string;
  xPos: number;
}) {
  let myShape = shape;
  let x = xPos;
  console.log(`绘制${myShape},x坐标:${x}`);
}

九、核心总结

  1. TypeScript 对象类型的基础声明使用 {},属性分隔符支持 ;,,赋值时需严格匹配「不多不少」的属性规则。
  2. 对象方法支持函数签名和箭头函数两种类型声明方式,属性类型可通过 [] 读取。
  3. typeinterface 均可定义对象类型别名,interface 支持继承属性的兼容。
  4. 可选属性用 ? 声明,本质等同于允许 undefined,使用前需做 undefined 校验;只读属性用 readonly 声明,仅约束属性本身的赋值。
  5. 索引类型用于处理动态属性名,支持字符串、数字、Symbol 三种类型,需遵循「数字索引服从字符串索引」的规则。
  6. 对象解构赋值的类型声明需在外部整体添加,解构内部的冒号用于重命名,而非类型指定。
相关推荐
满天星辰2 小时前
Typescript的infer到底怎么使用?
前端·typescript
amazing-yuan4 小时前
彻底解决该 TS 报错 + 提升编译效率
前端·javascript·vue.js·typescript·vue·异常报错处理
小二·5 小时前
Python Web 开发进阶实战:前端现代化 —— Vue 3 + TypeScript 重构 Layui 界面,打造高性能 SPA
前端·python·typescript
刘一说20 小时前
TypeScript 与 JavaScript:现代前端开发的双子星
javascript·ubuntu·typescript
孟无岐20 小时前
【Laya】Component 使用说明
typescript·游戏引擎·游戏程序·laya
EndingCoder21 小时前
类的继承和多态
linux·运维·前端·javascript·ubuntu·typescript
码界奇点1 天前
基于Vue3与TypeScript的后台管理系统设计与实现
前端·javascript·typescript·vue·毕业设计·源代码管理
Wect1 天前
LeetCode 274. H 指数:两种高效解法全解析
算法·typescript
咖啡の猫1 天前
TypeScript-Babel
前端·javascript·typescript