对象类型基础
本教程共 80 篇 · 第 23 篇 · 更新于 2026-08-10 · 约 8 分钟阅读
本节目标:学会用类型注解描述一个对象的形状,理解可选属性的写法,搞懂 TypeScript 的”多余属性检查”是怎么回事以及什么时候会触发。
写 JavaScript 的时候,对象无处不在——配置项、函数参数、API 返回值,全都是对象。TypeScript 的核心能力之一,就是帮你精确描述”这个对象长什么样”。
对象字面量类型注解
最直接的方式,就是在变量后面用花括号写出类型:
// 声明一个 point 变量,要求它必须有两个数字属性 x 和 y
let point: { x: number; y: number } = { x: 10, y: 20 };
属性之间用分号或逗号分隔都行,TypeScript 两种写法都认。但分号更常见,看起来也更像”类型定义”而非”对象字面量”。
函数参数也可以用同样的写法:
function printPoint(pt: { x: number; y: number }) {
console.log(`坐标:(${pt.x}, ${pt.y})`);
}
printPoint({ x: 3, y: 7 }); // 正确
这种内联写法适合只用一两次的类型。如果同一个形状到处用,就该考虑提成接口(下一章讲)。
缺属性就报错
如果你给的对象少写了某个属性,编译器会立刻指出来:
printPoint({ x: 5 });
// 报错:类型 "{ x: number; }" 中缺少属性 "y"
反过来,多写属性呢?这里有个稍微反直觉的地方,我们马上讲。
可选属性
有些属性不是非填不可。用 ? 标记:
function printName(obj: { first: string; last?: string }) {
console.log(obj.last ? `${obj.first} ${obj.last}` : obj.first);
}
printName({ first: "Alice" }); // ✅ "Alice"
printName({ first: "Bob", last: "Smith" }); // ✅ "Bob Smith"
NoteTypeScript 7.0 默认开启
strict: true,连带开启了strictNullChecks。这意味着可选属性的类型会自动包含undefined——所以last的类型实际上是string | undefined。
可选属性有几个实用场景:
- 配置对象:不是每个配置项都要传,用默认值兜底就行。
- “后面再补”的模式:先创建对象,后面再给可选属性赋值。
- 防止拼写错误:如果某个属性是可选的,写错属性名(比如打成
lst)编译器会报,因为它不在类型定义里。
结构类型:TypeScript 的”鸭子”
TypeScript 判断两个对象类型是否兼容,不看名字,只看形状。这叫做结构化类型(structural typing),也叫”鸭子类型”——走起来像鸭子、叫起来像鸭子,那就是鸭子。
interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
let p2: Point2D = { x: 1, y: 2 };
let p3: Point3D = { x: 1, y: 2, z: 3 };
// ✅ Point3D 包含了 Point2D 的所有属性,所以可以赋值
p2 = p3;
// ❌ Point2D 缺少 z,反过来不行
// p3 = p2;
这跟 Java 或 C# 那种”必须显式声明实现了某个接口”的风格完全不同。在 TypeScript 里,只要形状对得上就能过——这让你可以在不引入任何类型声明的情况下,把普通 JS 对象传给 TS 函数。
多余属性检查
刚才说过”多写属性”——现在来揭开谜底。
看这段代码,你觉得会报错吗?
function printPoint(pt: { x: number; y: number }) {
console.log(`(${pt.x}, ${pt.y})`);
}
printPoint({ x: 3, y: 7, color: "red" });
// ❌ 报错:"color" 不存在于类型 "{ x: number; y: number; }" 中
报了。 但你可能会想:刚才不是说结构化类型”多属性也可以”吗?p3 赋值给 p2 明明没报错。
区别在于:多余属性检查只针对”对象字面量直接传入”的情况。 TypeScript 的设计者认为,当你把一个刚写出来的对象字面量传给函数时,多余的属性大概率是拼写错误或者误解了 API——检查一下能救你很多次。
如果你先把对象存到变量里再传,就绕过了这个检查:
const myPoint = { x: 3, y: 7, color: "red" };
printPoint(myPoint); // ✅ 不报错!因为 myPoint 的类型被推断为 { x: number; y: number; color: string }
这是因为变量 myPoint 已经有了自己的类型(通过类型推断得出),传给 printPoint 时走的是正常的结构化类型兼容检查,不会触发多余属性检查。
三种绕过方法
如果你真的要传一个带额外属性的对象字面量,有几种办法:
方法一:用变量中转(如上例)。
方法二:类型断言。
printPoint({ x: 3, y: 7, color: "red" } as { x: number; y: number });
断言告诉编译器”我比你更清楚这是什么类型”,多余属性检查会被跳过。但要小心——这相当于关了这层保护。
方法三:加索引签名(提前声明”允许额外属性”)。
interface PointWithExtra {
x: number;
y: number;
[key: string]: unknown; // 允许任意字符串键
}
function printFlexPoint(pt: PointWithExtra) {
console.log(`(${pt.x}, ${pt.y})`);
}
printFlexPoint({ x: 3, y: 7, color: "red" }); // ✅
关于索引签名的详细用法,我们会在第 24 章和第 26 章细讲。
嵌套对象
类型的嵌套是直接写在花括号里的:
function printUser(user: {
name: string;
address: {
street: string;
city: string;
zipCode: number;
};
}) {
console.log(`${user.name} 住在 ${user.address.city}`);
}
嵌套层级多了可读性会崩。这种情况就该用接口或类型别名把结构拆开——第 24 章马上讲。
小结
- TypeScript 用
{ x: number; y: number }的语法给对象做类型注解。 - 可选属性用
?标记,在 strict 模式下类型自动包含undefined。 - TypeScript 是结构化类型系统——只看形状不看名字。
- 多余属性检查只在对象字面量直接传入时触发,目的是防拼写错误。
- 需要绕过时可以用变量中转、类型断言或索引签名。
搞懂了对象字面量类型注解,下一章我们来看 TypeScript 专门为对象设计的一等公民——接口(interface)。