TypeScript: TypeScript 对象类型
最后更新:2026-08-26
在 JavaScript 中,对象是最常用的数据结构。TypeScript 的对象类型让你精确描述"一个对象应该有什么属性、什么类型"——这是结构化类型系统的核心。
1. 对象类型基础
(1) 匿名对象类型
最直接的写法——直接在类型位置写出对象的结构:
TYPESCRIPT
let user: { name: string; age: number } = {
name: "Charlie",
age: 20
};
(2) 类型推断
对象字面量赋值给变量时,TypeScript 会自动推断类型:
TYPESCRIPT
let user = {
name: "Charlie",
age: 20
};
// 推断为 { name: string; age: number }
user.name = "Diana"; // ✅
user.age = 21; // ✅
// user.email = "x@x.com"; // ❌ 推断的类型中没有 email 属性
(3) 多行写法
对象类型属性多时,推荐多行写法(分号或逗号分隔都可以):
TYPESCRIPT
let product: {
id: number;
name: string;
price: number;
inStock: boolean;
} = {
id: 1,
name: "TypeScript权威指南",
price: 89.9,
inStock: true
};
▶ 示例:函数参数的对象类型
TYPESCRIPT
function createUser(user: { name: string; age: number; email: string }): string {
return `${user.name},${user.age}岁,邮箱:${user.email}`;
}
console.log(createUser({
name: "Charlie",
age: 20,
email: "xiaoming@example.com"
}));
输出:
TEXT
📖 仅展示
Charlie,20岁,邮箱:xiaoming@example.com
2. 可选属性
用 ? 标记可选属性——该属性可以不存在:
(1) 语法
TYPESCRIPT
let user: { name: string; age?: number } = {
name: "Charlie"
// age 可以不提供
};
console.log(user.name); // "Charlie"
console.log(user.age); // undefined
(2) 可选属性的类型
可选属性的类型自动包含 undefined:
TYPESCRIPT
// age?: number 等价于 age: number | undefined
// 但有重要区别:age?: number 允许属性不存在,age: number | undefined 要求属性必须存在
let config: { host: string; port?: number } = { host: "localhost" };
// config.port 是 undefined(属性不存在也是 ok 的)
let config2: { host: string; port: number | undefined } = { host: "localhost" };
// ❌ 报错!port 属性必须存在,即使是 undefined 也要显式写上
// 正确写法:{ host: "localhost", port: undefined }
(3) 访问可选属性的安全方式
TYPESCRIPT
interface User {
name: string;
age?: number;
}
function getAgeText(user: User): string {
// ❌ 不安全——如果 age 不存在,undefined.toFixed() 会运行时崩溃
// return user.age.toFixed(0) + "岁";
// ✅ 方式一:先检查
if (user.age !== undefined) {
return user.age.toFixed(0) + "岁";
}
return "年龄未知";
// ✅ 方式二:提供默认值
// return (user.age ?? 0).toFixed(0) + "岁";
}
3. 只读属性
用 readonly 标记只读属性——赋值后不可修改:
TYPESCRIPT
let user: { readonly id: number; name: string } = {
id: 1,
name: "Charlie"
};
user.name = "Diana"; // ✅ 普通属性可以修改
// user.id = 2; // ❌ 只读属性不可修改
(2) readonly 是浅层只读
TYPESCRIPT
let config: { readonly data: { value: number } } = {
data: { value: 42 }
};
// config.data = { value: 100 }; // ❌ data 本身不可重新赋值
config.data.value = 100; // ✅ data 内部的属性仍然可修改!
💡 深层只读: 如果需要对象所有层级都只读,用
Readonly<类型> 工具类型(嵌套使用)或 as const 断言。第19课会介绍 Readonly、Partial、Required 等工具类型。
4. 索引签名
当对象的属性名不确定,但属性值的类型确定时,用索引签名:
(1) 字符串索引签名
TYPESCRIPT
let scores: { [key: string]: number } = {
语文: 90,
数学: 95,
英语: 88
};
console.log(scores["数学"]); // 95
// 添加新属性——只要是 string 键 + number 值就行
scores["物理"] = 85; // ✅
(2) 数字索引签名
TYPESCRIPT
let arr: { [index: number]: string } = {
0: "first",
1: "second",
2: "third"
};
console.log(arr[1]); // "second"
(3) 索引签名的限制
索引签名要求所有已知属性的类型必须是索引值类型的子类型:
TYPESCRIPT
// ❌ 错误:name 是 string,但索引签名要求所有值是 number
// let config: { [key: string]: number; name: string } = { name: "test" };
// ✅ 正确:所有值类型一致
let config: { [key: string]: string; name: string } = { name: "test" };
(4) 模板字面量索引签名(TypeScript 4.4+)
TYPESCRIPT
let css: { [key: `--${string}`]: string } = {
"--primary-color": "#4A90D9",
"--font-size": "14px"
};
▶ 示例:用索引签名实现动态配置
TYPESCRIPT
interface EnvConfig {
[key: string]: string | number | boolean;
// 所有值可以是 string、number 或 boolean
// 已知属性也必须兼容
NODE_ENV: string;
PORT: number;
}
let env: EnvConfig = {
NODE_ENV: "development",
PORT: 3000,
DEBUG: true, // ✅ 动态属性
API_URL: "http://localhost:8080" // ✅ 动态属性
};
console.log(`环境:${env.NODE_ENV}`);
console.log(`端口:${env.PORT}`);
console.log(`调试:${env.DEBUG}`);
输出:
TEXT
📖 仅展示
环境:development
端口:3000
调试:true
5. 嵌套对象类型
实际开发中,对象经常多层嵌套——TypeScript 支持逐层描述类型:
(1) 嵌套对象字面量类型
TYPESCRIPT
let user: {
name: string;
address: {
city: string;
zip: string;
};
tags: string[];
} = {
name: "Charlie",
address: {
city: "北京",
zip: "100000"
},
tags: ["开发者", "TypeScript"]
};
console.log(user.address.city); // "北京"
console.log(user.tags[0]); // "开发者"
(2) 多层嵌套
TYPESCRIPT
type ApiResponse = {
status: number;
data: {
user: {
id: number;
profile: {
avatar: string;
bio: string;
};
};
};
};
let response: ApiResponse = {
status: 200,
data: {
user: {
id: 1,
profile: {
avatar: "https://example.com/avatar.png",
bio: "TypeScript 爱好者"
}
}
}
};
console.log(response.data.user.profile.bio); // "TypeScript 爱好者"
💡 建议: 嵌套超过2-3层时,应该把内层类型提取为独立的 interface 或 type 别名,增强可读性和复用性。第9-11课会详细讲解。
6. 对象类型的两种风格
(1) 匿名类型(内联)
TYPESCRIPT
function greet(user: { name: string; age: number }): string {
return `你好,${user.name}!`;
}
适合:只用一次的简单类型、函数参数
(2) 命名类型(interface / type)
TYPESCRIPT
interface User {
name: string;
age: number;
}
function greet(user: User): string {
return `你好,${user.name}!`;
}
适合:多处复用、属性较多、需要继承/扩展
📌 建议: 属性超过3个或类型需要复用时,提取为 interface 或 type 别名。简单的一次性参数可以用匿名类型。
7. 结构化类型系统
TypeScript 采用结构化类型系统——只要对象的结构满足类型要求就兼容,不要求类型名称相同:
(1) 结构匹配
TYPESCRIPT
interface Point {
x: number;
y: number;
}
let point: Point = { x: 1, y: 2 }; // ✅ 有 x 和 y
let point3D = { x: 1, y: 2, z: 3 };
point = point3D; // ✅ point3D 有 x 和 y,多余属性不冲突
(2) 多余属性检查
对象字面量直接赋值时,TypeScript 会执行"多余属性检查"——不允许有目标类型中不存在的属性:
TYPESCRIPT
interface Point {
x: number;
y: number;
}
// ❌ 多余属性检查——直接赋值对象字面量时不允许有额外属性
// let p: Point = { x: 1, y: 2, z: 3 };
// ✅ 方式一:通过变量绕过(因为变量不会触发多余属性检查)
let temp = { x: 1, y: 2, z: 3 };
let p: Point = temp; // ✅
// ✅ 方式二:用类型断言
let p2: Point = { x: 1, y: 2, z: 3 } as Point;
// ✅ 方式三:扩展接口(最推荐)
interface Point3D extends Point {
z: number;
}
let p3: Point3D = { x: 1, y: 2, z: 3 };
💡 为什么要多余属性检查? 这是为了捕获拼写错误。如果你写
{ x: 1, y: 2, zzz: 3 },TypeScript 会立刻提醒你——zzz 不是 Point 的属性,你是不是拼错了?
▶ 示例:结构化类型实战
TYPESCRIPT
interface Printable {
toString(): string;
}
// Date 有 toString 方法,结构匹配
let date: Printable = new Date();
// 自定义对象有 toString 方法,也匹配
let custom: Printable = {
toString() { return "自定义对象"; }
};
function print(obj: Printable): void {
console.log(obj.toString());
}
print(date); // 输出 Date 的字符串表示
print(custom); // 输出 "自定义对象"
❓ 常见问题
Q 对象类型用 interface 还是 type 别名?
A 两者大部分场景等价。interface 更适合定义对象形状(支持声明合并、extends 继承),type 更适合联合类型、条件类型等复杂操作。团队统一即可,本章后续会详细对比。初学者推荐从 interface 开始。
Q 可选属性和 undefined 联合类型有什么区别?
A
age?: number 允许属性不存在(完全缺失),age: number | undefined 要求属性必须存在但值可以是 undefined。实际开发中,可选属性更常用——省略属性比显式写 undefined 更自然。Q 为什么对象字面量有多余属性检查,变量却没有?
A 对象字面量直接赋值时,开发者通常很清楚自己写了什么——多余属性大概率是拼写错误。变量赋值时,变量的类型可能来自其他地方,多余属性可能是合理的(子类型多出的属性)。这是 TypeScript 在"安全"和"灵活"之间的权衡。
Q readonly 属性真的不可修改吗?
A readonly 只是编译时检查,运行时没有任何防护。通过类型断言或 any 可以绕过。但它能有效防止"意外修改"——这是类型系统的主要目的:在编译时捕获错误,而不是运行时防护。
📖 小节
- 对象类型描述"对象有哪些属性、属性是什么类型",用
{ 属性名: 类型 }语法 ?标记可选属性,readonly标记只读属性;readonly 是浅层只读- 索引签名
[key: 类型]: 值类型描述"键类型不确定但值类型确定"的对象 - 嵌套对象逐层描述类型,超过2-3层建议提取为独立的 interface/type
- TypeScript 采用结构化类型系统——结构匹配就兼容;对象字面量直接赋值有多余属性检查
📝 作业
- 基础题(难度⭐):定义一个对象类型描述"图书"(title、author、price、inStock?),创建两本书的对象——一本有 inStock,一本没有,输出它们的标题和价格。
- 进阶题(难度⭐⭐):写一个函数
updateUser(user: { name: string; age: number; email?: string }, updates: { age?: number; email?: string }),返回更新后的用户对象。要求原 user 的 name 不可变(用 readonly)。 - 挑战题(难度⭐⭐⭐):用索引签名定义一个
CacheStore类型,键是 string,值是{ data: T; timestamp: number }(T 是泛型)。创建一个CacheStore<string>实例,存入两条缓存数据,然后遍历输出所有缓存的键和过期状态(假设5秒过期)。