首页 / TypeScript 入门教程 / 对象类型基础

TypeScript 入门教程

对象类型基础

本教程共 80 篇 · 第 23 篇 · 更新于 2026-08-10 · 约 8 分钟阅读

TypeScriptTypeScript 入门教程对象类型结构化类型

本节目标:学会用类型注解描述一个对象的形状,理解可选属性的写法,搞懂 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"
Note

TypeScript 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)。