

我们已经了解，联合类型 (`|`) 为 TypeScript 带来了强大的灵活性，允许一个变量拥有多种可能性。但这种灵活性也带来了一个挑战：当我们持有一个联合类型的值时，如何安全地访问其特定类型的属性或方法？

直接访问是行不通的，因为 TypeScript 编译器无法保证在运行时变量的具体类型，为了防止错误，它只会允许我们访问所有联合成员的**共有**属性。要突破这一限制，我们就需要一种机制来向编译器“证明”在某个代码块中，变量是某个具体的类型。这个机制，就是**类型守卫 (Type Guards)**。

类型守卫本质上是一个返回布尔值的运行时检查，但它的特殊之处在于，TypeScript 能够理解这些检查，并在它们返回 `true` 的代码块内，智能地**收窄 (narrow)** 变量的类型。让我们来深入探索几种最核心的类型守卫。

### 1. `typeof` 守卫：原始类型的辨识专家

当处理的联合类型包含 `string`、`number`、`boolean`、`symbol`、`bigint`、`undefined` 或 `function` 等原始类型时，`typeof` 操作符是最直接、最有效的类型守卫。

```typescript
function processInput(input: string | number | string[]) {
  if (typeof input === "string") {
    // 在此代码块内，TypeScript 确信 input 是 string
    console.log(input.toUpperCase());
  } else if (typeof input === "number") {
    // 在此代码块内，TypeScript 确信 input 是 number
    console.log(input.toFixed(2));
  } else {
    // 在此代码块内，TypeScript 推断 input 是 string[]
    console.log(`Array length: ${input.length}`);
  }
}
```
值得注意的是，`typeof null` 在 JavaScript 中返回 `"object"`，这是一个历史遗留问题。因此，如果你的联合类型中包含 `null`，需要用 `input === null` 这样的相等性检查来单独处理。

### 2. `instanceof` 守卫：类实例的身份检查器

`typeof` 对所有非函数对象的返回值都是 `"object"`，这使得它无法区分两个不同的类实例。当我们需要检查一个对象是否由某个特定的类创建时，`instanceof` 守卫便派上了用场。它通过检查对象的原型链来判断其“血统”。

让我们设想一个媒体播放器应用，它可以处理 `Song` 和 `Album` 两种对象：

```typescript
class Song {
  constructor(public title: string, public duration: number) {}
  play() {
    console.log(`Playing song: ${this.title}`);
  }
}

class Album {
  constructor(public name: string, public songs: Song[]) {}
  listSongs() {
    console.log(`Songs in album ${this.name}:`);
    this.songs.forEach(song => console.log(`- ${song.title}`));
  }
}

function playMedia(media: Song | Album) {
  if (media instanceof Song) {
    // 在此代码块内，TypeScript 知道 media 是 Song 的实例
    media.play();
  } else if (media instanceof Album) {
    // 在此代码块内，TypeScript 知道 media 是 Album 的实例
    media.listSongs();
  }
}
```
`instanceof` 是处理面向对象编程中多态行为的理想工具，它清晰地划分了不同类实例的处理逻辑。

### 3. 自定义类型守卫：打造你自己的类型验证器

`typeof` 和 `instanceof` 功能强大，但它们无法处理基于接口（`interface`）或纯对象字面量定义的类型。当我们需要根据对象的结构特征（比如是否存在某个属性）来区分类型时，就需要创建**自定义类型守卫**。

自定义类型守卫本质上是一个函数，其特殊之处在于它的返回值类型是一个**类型谓词 (Type Predicate)**，形如 `parameterName is Type`。

这个特殊的返回类型向 TypeScript 传达了一个明确的信号：“如果这个函数返回 `true`，那么你可以确信传入的参数就是 `Type` 类型。”

让我们来看一个经典的动物例子：

```typescript
interface Fish {
  swim(): void;
}

interface Bird {
  fly(): void;
}

// 这就是一个自定义类型守卫函数
function isFish(pet: Fish | Bird): pet is Fish {
  // 我们通过检查 pet 是否有 swim 方法来判断它是不是 Fish
  // (pet as Fish).swim 是一种类型断言，确保 swim 属性可被访问
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    // 在此代码块内，TypeScript 因为 isFish 返回 true，
    // 所以确信 pet 的类型是 Fish
    pet.swim();
  } else {
    // 否则，TypeScript 推断 pet 的类型是 Bird
    pet.fly();
  }
}
```
通过 `pet is Fish` 这个返回类型，`isFish` 函数不仅仅返回了一个布尔值，它还直接参与到了 TypeScript 的类型流分析中，为我们编写的 `if` 语句赋予了类型收窄的能力。

### 其他守卫技术

除了上述三种核心守卫，还有一些常用的模式也利用了类型收窄的原理：

*   **`in` 操作符**：通过检查属性是否存在来区分类型，例如 `if ("swim" in pet)`。
*   **相等性检查**：当与字面量类型结合时，`===`, `==`, `!==`, `!=` 也能作为类型守卫。这正是**可辨识联合**模式的核心，通过检查一个共享的“标签”属性（如 `shape.kind === "circle"`）来实现最安全、最清晰的类型收窄。

### 总结

类型守卫是 TypeScript 中连接动态世界与静态类型安全世界的桥梁。它们让我们能够在享受联合类型带来的灵活性的同时，通过在运行时进行必要的检查，来向编译器提供足够的信息，从而在特定的代码区域内恢复类型的精确性。

*   使用 **`typeof`** 来处理原始类型。
*   使用 **`instanceof`** 来处理类实例。
*   使用**自定义类型守卫** (`pet is Fish`) 来处理更复杂的对象结构和业务逻辑。

熟练掌握并运用这些类型守卫，是编写出健壮、可读且真正能发挥 TypeScript 优势的代码的关键。它们是你在处理不确定的外部数据（如 API 响应）和复杂的内部状态时，不可或缺的安全保障。

