TypeScript 完全指南(中):函数、接口、类与高级类型
2026/6/1 4:17:57 网站建设 项目流程

承接上篇:掌握了基础类型后,我们来学习 TypeScript 中真正让代码变得可扩展、可复用的核心特性。

上篇我们学会了给变量、数组、对象标注类型。但如果你的代码只用到基础类型,那 TypeScript 的威力连十分之一都没发挥出来。真正让 TypeScript 与众不同的,是它能够描述函数的行为、定义对象的结构(接口)、实现面向对象编程(类)以及用高级类型进行类型编程。

本文将聚焦这些核心内容,全程纯 TypeScript,不涉及任何前端框架。

第四部分:函数——类型系统的“连接器”

函数是 JavaScript 的“一等公民”,TypeScript 为函数提供了丰富的类型注解方式。

4.1 函数参数与返回值的类型注解

最简单的函数类型注解包括参数类型和返回值类型:

typescript

// 语法:(参数名: 类型, 参数名: 类型): 返回值类型 function add(x: number, y: number): number { return x + y; } const result = add(3, 5); // result 被推断为 number

如果函数没有返回值,使用void

typescript

function logMessage(msg: string): void { console.log(msg); // 没有 return 语句,或 return undefined; }

4.2 函数类型表达式

你可以用变量来存储函数,此时需要描述这个变量的类型:

typescript

// 定义一个函数类型:接受两个 number,返回 number let myAdd: (x: number, y: number) => number; // 赋值一个符合该类型的函数 myAdd = function (a: number, b: number): number { return a + b; }; // 也可以使用类型别名简化 type MathOperation = (a: number, b: number) => number; let multiply: MathOperation = (x, y) => x * y;

4.3 可选参数和默认参数

TypeScript 中的每个函数参数都是必传的,除非标记为可选或提供默认值。

可选参数:使用?标记,必须放在必选参数的后面。

typescript

function buildName(firstName: string, lastName?: string): string { if (lastName) { return `${firstName} ${lastName}`; } return firstName; } console.log(buildName("Alice")); // "Alice" console.log(buildName("Alice", "Chen")); // "Alice Chen" // console.log(buildName("Alice", "Chen", "Jr")); // ❌ 参数过多

默认参数:为参数提供默认值,此时参数自动变为可选。

typescript

function greet(name: string, greeting: string = "Hello"): string { return `${greeting}, ${name}!`; } console.log(greet("Bob")); // "Hello, Bob!" console.log(greet("Bob", "Hi")); // "Hi, Bob!"

4.4 剩余参数

使用...rest: Type[]语法接收任意数量的参数:

typescript

function sumNumbers(...numbers: number[]): number { return numbers.reduce((total, num) => total + num, 0); } console.log(sumNumbers(1, 2, 3, 4, 5)); // 15 console.log(sumNumbers(10, 20)); // 30

4.5 函数重载

函数重载允许同一个函数根据不同的参数类型或数量,执行不同的逻辑,并提供准确的类型提示。

重载的语法:先写多个重载签名(只有类型,没有实现),然后写一个通用的实现签名。

typescript

// 重载签名 function formatValue(value: string): string; function formatValue(value: number): string; function formatValue(value: boolean): string; // 实现签名(必须兼容所有重载) function formatValue(value: string | number | boolean): string { if (typeof value === "string") { return `"${value}"`; } else if (typeof value === "number") { return value.toFixed(2); } else { return value ? "true" : "false"; } } console.log(formatValue("hello")); // 输出 "hello" console.log(formatValue(3.1415)); // 输出 3.14 console.log(formatValue(true)); // 输出 true

注意:TypeScript 会根据你传入的参数类型,匹配到对应的重载签名,从而给出准确的返回类型。上面的例子中,formatValue("hi")的返回类型被推断为string

4.6 函数中的this类型

在 JavaScript 中,this的指向容易出错。TypeScript 允许你在函数参数中声明this的类型(第一个参数,名字必须叫this)。

typescript

interface User { name: string; age: number; } function describe(this: User): void { console.log(`${this.name} is ${this.age} years old`); } const user: User = { name: "Alice", age: 30 }; describe.call(user); // 正确调用 // describe(); // ❌ 错误:this 上下文丢失

通常你不需要手动标注this,但在事件监听器、类方法等场景中非常有用。

第五部分:接口——定义对象的形状

接口是 TypeScript 中最核心的概念之一,它用于定义对象的结构(即这个对象应该有哪些属性,每个属性的类型是什么)。

5.1 基本接口语法

typescript

interface Person { name: string; age: number; email: string; } // 使用接口 const alice: Person = { name: "Alice", age: 25, email: "alice@example.com" }; // 缺少属性会报错 // const bob: Person = { name: "Bob", age: 30 }; // ❌ 缺少 email

5.2 可选属性和只读属性

  • 可选属性:用?标记,表示该属性可以不存在。

  • 只读属性:用readonly标记,表示初始化后不可修改。

typescript

interface Product { readonly id: number; // 只读 name: string; price: number; description?: string; // 可选 } const phone: Product = { id: 1001, name: "Smartphone", price: 599 // description 可以省略 }; // phone.id = 1002; // ❌ 错误:id 是只读的 phone.price = 499; // ✅ 可以修改非只读属性

5.3 多余属性检查

当将一个对象字面量直接赋值给接口类型的变量时,TypeScript 会进行多余属性检查:不允许出现接口中未定义的属性。

typescript

interface Point { x: number; y: number; } // 错误:对象字面量只能指定已知属性 // const p: Point = { x: 10, y: 20, z: 30 }; // ❌ 'z' 不在 Point 中 // 绕过多余属性检查的几种方式: // 1. 使用类型断言 const p1 = { x: 10, y: 20, z: 30 } as Point; // 2. 先赋值给另一个变量 const temp = { x: 10, y: 20, z: 30 }; const p2: Point = temp; // ✅ 没有直接使用字面量,不会触发多余属性检查 // 3. 使用索引签名(见下文)

5.4 索引签名

当你不确定对象会有哪些属性,但知道属性值的类型时,可以使用索引签名。

typescript

// 字符串索引签名:所有属性值必须是 string interface StringDictionary { [key: string]: string; } const dict: StringDictionary = { hello: "你好", world: "世界" // age: 25 // ❌ 错误:数字不能赋给 string }; // 数字索引签名(通常用于类数组对象) interface NumberArray { [index: number]: string; } const arr: NumberArray = ["a", "b", "c"]; console.log(arr[0]); // "a"

注意:同时存在字符串索引和数字索引时,数字索引的返回值类型必须是字符串索引返回值类型的子类型(因为 JavaScript 会将数字索引转换为字符串)。

5.5 函数类型接口

接口不仅可以描述对象,还可以描述函数类型:

typescript

interface GreetFunction { (name: string, greeting?: string): string; } const greet: GreetFunction = (name, greeting = "Hello") => { return `${greeting}, ${name}!`; };

5.6 接口继承

接口可以继承另一个或多个接口,把它们的属性合并过来。

typescript

interface Animal { name: string; age: number; } interface Dog extends Animal { breed: string; bark(): void; } const myDog: Dog = { name: "Buddy", age: 3, breed: "Golden Retriever", bark() { console.log("Woof!"); } }; // 多继承 interface Swimmer { swim(): void; } interface Flyer { fly(): void; } interface Duck extends Swimmer, Flyer { quack(): void; }

5.7 接口与类型别名的区别

回顾上篇我们讲了类型别名(type),它也能描述对象形状。什么时候用接口,什么时候用类型别名?

特性interfacetype
声明合并✅ 支持(多次定义同名接口会自动合并)❌ 不支持
继承/扩展extends语法交叉类型&
描述对象/函数✅ 专门为此设计✅ 也可以
描述联合类型❌ 不能✅ 可以
描述元组❌ 不能(TS 4.2+ 可以部分用,但不推荐)✅ 可以
实现(implements)类可以实现多个接口类不能实现类型别名(但可以 implements 交叉类型)

经验法则:默认使用interface,直到你需要使用type的独有特性(如联合类型、映射类型等)。

typescript

// 声明合并示例 interface User { name: string; } interface User { age: number; } // 最终 User 接口包含 name 和 age const u: User = { name: "Alice", age: 25 };

第六部分:类——面向对象的 TypeScript

TypeScript 对 ES6 类进行了增强,加入了访问修饰符、抽象类、只读属性等面向对象特性。

6.1 基础类语法

typescript

class Animal { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } speak(): void { console.log(`${this.name} makes a sound.`); } } const cat = new Animal("Whiskers", 2); cat.speak();

6.2 访问修饰符

TypeScript 提供了三个访问修饰符:

  • public:默认,任何地方都可访问。

  • private:只能在类内部访问。

  • protected:能在类内部及子类中访问,但不能在实例中访问。

typescript

class Person { public name: string; // 公开 private age: number; // 私有 protected id: number; // 受保护 constructor(name: string, age: number, id: number) { this.name = name; this.age = age; this.id = id; } public getAge(): number { return this.age; // 内部可以访问 private } } class Employee extends Person { private department: string; constructor(name: string, age: number, id: number, department: string) { super(name, age, id); this.department = department; } public getInfo(): string { // return this.age; // ❌ private,不能访问 return `${this.name} (ID: ${this.id})`; // ✅ protected 可以访问 } } const emp = new Employee("Bob", 30, 12345, "IT"); console.log(emp.name); // ✅ public // console.log(emp.age); // ❌ private // console.log(emp.id); // ❌ protected

6.3 参数属性(Parameter Properties)

TypeScript 提供了一种简写:在构造函数参数前加上修饰符,可以自动声明并初始化同名字段。

typescript

class User { // 相当于声明了 public name: string,并在构造函数中赋值 constructor(public name: string, private age: number) {} } const u = new User("Alice", 25); console.log(u.name); // ✅ // console.log(u.age); // ❌ private

6.4 只读属性

使用readonly关键字,属性只能在初始化时赋值,之后不可修改。

typescript

class Book { readonly isbn: string; title: string; constructor(isbn: string, title: string) { this.isbn = isbn; this.title = title; } } const book = new Book("123456", "TypeScript Guide"); // book.isbn = "654321"; // ❌ 错误 book.title = "New Title"; // ✅ 可以修改非 readonly 属性

6.5 抽象类

抽象类不能直接实例化,必须被继承。它可以包含抽象方法(没有方法体,子类必须实现)和具体方法。

typescript

abstract class Shape { abstract getArea(): number; // 抽象方法,没有实现 describe(): string { // 具体方法,可以被子类继承 return `This shape has area: ${this.getArea()}`; } } class Circle extends Shape { constructor(private radius: number) { super(); } getArea(): number { return Math.PI * this.radius ** 2; } } class Square extends Shape { constructor(private side: number) { super(); } getArea(): number { return this.side ** 2; } } // const s = new Shape(); // ❌ 错误:抽象类不能实例化 const circle = new Circle(5); console.log(circle.getArea()); // 78.539... console.log(circle.describe()); // "This shape has area: 78.539..."

6.6 类实现接口

类可以使用implements关键字来实现一个或多个接口,强制类包含接口中定义的所有属性/方法。

typescript

interface ClockInterface { currentTime: Date; setTime(d: Date): void; } class Clock implements ClockInterface { currentTime: Date; constructor(h: number, m: number) { this.currentTime = new Date(); } setTime(d: Date): void { this.currentTime = d; } }

类也可以实现多个接口:

typescript

interface Printable { print(): void; } interface Serializable { serialize(): string; } class Document implements Printable, Serializable { print(): void { console.log("Printing..."); } serialize(): string { return "document data"; } }

6.7 静态成员

静态属性和方法属于类本身,而不是实例。使用static关键字。

typescript

class MathUtils { static PI: number = 3.14159; static circleArea(radius: number): number { return this.PI * radius ** 2; } } console.log(MathUtils.PI); // 3.14159 console.log(MathUtils.circleArea(5)); // 78.53975

第七部分:高级类型——类型编程的艺术

前面我们学了基础类型、函数、接口和类。这一部分将进入 TypeScript 的“高级玩法”:交叉类型、类型保护、类型操作符、映射类型、条件类型等。

7.1 交叉类型(&

交叉类型将多个类型合并为一个类型,包含所有类型的成员。常用于混入(mixin)。

typescript

interface A { a: string; } interface B { b: number; } type C = A & B; // 既有 a 又有 b const obj: C = { a: "hello", b: 42 }; // 更复杂的例子:合并函数签名 type Admin = { name: string; privileges: string[] }; type Employee = { name: string; startDate: Date }; type ElevatedEmployee = Admin & Employee; const e: ElevatedEmployee = { name: "Alice", privileges: ["create-server"], startDate: new Date() };

注意:交叉类型可能会产生不可调和的矛盾,比如string & number会变成never

7.2 类型保护(Type Guards)

类型保护是运行时检查,用于在特定作用域内收窄类型。

typeof类型保护

typescript

function padLeft(value: string | number, padding: number): string { if (typeof value === "string") { // 这里 value 是 string return value.padStart(padding, " "); } else { // 这里 value 是 number return value.toString().padStart(padding, " "); } }

instanceof类型保护

typescript

class Bird { fly() { console.log("Flying"); } } class Fish { swim() { console.log("Swimming"); } } function move(animal: Bird | Fish) { if (animal instanceof Bird) { animal.fly(); } else { animal.swim(); } }

自定义类型保护:使用value is Type语法定义函数,返回布尔值,告诉 TS 类型收窄。

typescript

interface Cat { meow(): void; } interface Dog { bark(): void; } function isCat(animal: Cat | Dog): animal is Cat { return (animal as Cat).meow !== undefined; } function makeSound(animal: Cat | Dog) { if (isCat(animal)) { animal.meow(); // TS 知道这里是 Cat } else { animal.bark(); // TS 知道这里是 Dog } }

7.3 类型操作符keyof

keyof操作符获取一个类型的所有属性名组成的联合类型。

typescript

interface Person { name: string; age: number; address: string; } type PersonKeys = keyof Person; // "name" | "age" | "address" function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const p: Person = { name: "Alice", age: 25, address: "123 St" }; const nameValue = getProperty(p, "name"); // string // const invalid = getProperty(p, "invalid"); // ❌ 错误

7.4 索引访问类型(T[K]

你可以像访问数组元素一样,获取类型的某个属性的类型。

typescript

interface Car { brand: string; year: number; owner: { name: string; age: number }; } type BrandType = Car["brand"]; // string type OwnerType = Car["owner"]; // { name: string; age: number } type OwnerAgeType = Car["owner"]["age"]; // number // 联合索引 type Keys = "brand" | "year"; type Values = Car[Keys]; // string | number

7.5 映射类型(Mapped Types)

映射类型基于旧类型创建新类型,语法:{ [P in K]: T[P] }

内置的几个常用映射类型:

typescript

// Partial<T>:所有属性变为可选 type PartialPerson = Partial<Person>; // { name?: string; age?: number; address?: string; } // Readonly<T>:所有属性变为只读 type ReadonlyPerson = Readonly<Person>; // { readonly name: string; readonly age: number; readonly address: string; } // Pick<T, K>:选取部分属性 type NameAndAge = Pick<Person, "name" | "age">; // { name: string; age: number; } // Record<K, T>:创建一个对象类型,键为 K,值为 T type Page = "home" | "about" | "contact"; type PageInfo = Record<Page, { title: string; url: string }>; // 等价于: // { home: {title, url}; about: {...}; contact: {...} }

你可以自己实现这些工具类型:

typescript

type MyPartial<T> = { [P in keyof T]?: T[P]; }; type MyReadonly<T> = { readonly [P in keyof T]: T[P]; };

7.6 条件类型(Conditional Types)

条件类型的语法类似于三元运算符:T extends U ? X : Y

typescript

type IsString<T> = T extends string ? true : false; type A = IsString<"hello">; // true type B = IsString<42>; // false // 使用 infer 提取类型 type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; function example(): number[] { return [1, 2, 3]; } type R = ReturnType<typeof example>; // number[] // 递归条件类型 type Flatten<T> = T extends any[] ? T[number] : T; type NumArr = Flatten<number[]>; // number type Str = Flatten<string>; // string

7.7 内置工具类型速查

TypeScript 提供了很多实用的工具类型,下面列举最常用的:

工具类型作用示例
Partial<T>将 T 的所有属性变为可选Partial<Person>
Required<T>将 T 的所有属性变为必选Required<PartialPerson>
Readonly<T>将 T 的所有属性变为只读Readonly<Person>
Pick<T, K>从 T 中选取指定的属性集 KPick<Person, "name">
Omit<T, K>从 T 中排除指定的属性集 KOmit<Person, "age">
Exclude<T, U>从 T 中排除可赋值给 U 的类型Exclude<"a"|"b", "a">"b"
Extract<T, U>从 T 中提取可赋值给 U 的类型Extract<"a"|"b", "a">"a"
NonNullable<T>从 T 中排除 null 和 undefinedNonNullable<string|null>string
ReturnType<T>获取函数 T 的返回值类型ReturnType<()=>number>number
Parameters<T>获取函数 T 的参数类型元组Parameters<(a: string)=>void>[string]
InstanceType<T>获取构造函数 T 的实例类型InstanceType<typeof Person>Person

示例用法:

typescript

interface Todo { title: string; description: string; completed: boolean; createdAt: Date; } // 创建一个只包含标题和完成状态的类型 type TodoPreview = Pick<Todo, "title" | "completed">; // 创建一个不包含描述的类型 type TodoWithoutDesc = Omit<Todo, "description">; // 将 Date 属性变为可选 type TodoWithOptionalDate = Partial<Pick<Todo, "createdAt">> & Omit<Todo, "createdAt">;

小结与下篇预告

本文(中篇)涵盖了

  • 函数:参数、返回值、可选/默认参数、剩余参数、重载

  • 接口:对象形状、可选/只读属性、索引签名、继承、与 type 的区别

  • 类:访问修饰符、参数属性、抽象类、实现接口、静态成员

  • 高级类型:交叉类型、类型保护、keyof、索引访问、映射类型、条件类型、内置工具类型

下篇(终篇)预告

  • 泛型的深入(泛型约束、泛型类、泛型与接口/类的结合)

  • 模块与命名空间

  • 声明文件(d.ts

  • 配置文件的深度解析

  • 实际项目中的最佳实践与性能优化

如果你已经掌握了中篇的内容,你完全有能力用 TypeScript 编写出结构清晰、类型安全的程序。请继续关注下篇,我们将把 TypeScript 的能力推向极致。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询