类型守卫与类型收窄(上)
本教程共 80 篇 · 第 51 篇 · 更新于 2026-08-10 · 约 12 分钟阅读
本节目标:掌握 TypeScript 的自动类型收窄机制——typeof、truthiness、equality、instanceof、in 五种内置收窄方式,理解编译器如何通过控制流分析自动推断更精确的类型。
什么是类型收窄
一个变量声明时类型可能是 string | number,但在某个 if 分支里,你通过代码逻辑可以确定它一定是 string。TypeScript 的编译器能”看懂”这种判断,并自动把变量的类型从宽泛的联合类型收窄为具体类型,这个过程叫类型收窄(Narrowing)。
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
// 编译器知道:这里 padding 一定是 number
return " ".repeat(padding) + input;
}
// 编译器知道:这里 padding 一定是 string
return padding + input;
}
上例中,typeof padding === "number" 就是一种类型守卫。TypeScript 根据它把 if 里的 padding 收窄为 number,else 里自动收窄为 string。
typeof 收窄
typeof 是最常用的类型守卫,TypeScript 能识别以下几种返回值:
"string"、"number"、"bigint"、"boolean"、"symbol""undefined"、"object"、"function"
function processValue(value: string | number | boolean) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // value 是 string
} else if (typeof value === "number") {
console.log(value.toFixed(2)); // value 是 number
} else {
console.log(value); // value 是 boolean
}
}
typeof 的坑:null 也是 “object”
function printAll(strs: string | string[] | null) {
if (typeof strs === "object") {
// strs 被收窄为 string[] | null,不是 string[]!
// 因为 typeof null === "object"
for (const s of strs) { // ❌ strs 可能是 null
console.log(s);
}
}
}
这是 JavaScript 的历史遗留问题。解决方案:先用 truthiness 检查干掉 null。
truthiness 收窄
JavaScript 中以下值在条件判断里被视为 false:0、NaN、""(空字符串)、0n、null、undefined。TypeScript 利用这一点做收窄:
function printAll(strs: string | string[] | null) {
if (strs) {
// strs 的真值检查排除了 null 和 undefined
// 但空字符串 "" 也被排除了!
}
}
一个更安全的版本——只排除 null 和 undefined:
function printAll(strs: string | string[] | null) {
if (strs && typeof strs === "object") {
// strs 一定是 string[]
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
}
}
!= null 的妙用
JavaScript 里 value != null 同时排除了 null 和 undefined(== null 的特殊行为),TypeScript 也识别这个模式:
interface Container {
value: number | null | undefined;
}
function multiplyValue(container: Container, factor: number) {
if (container.value != null) {
// value 被收窄为 number
container.value *= factor;
}
}
equality 收窄
用 ===、!==、==、!= 以及 switch 语句做类型收窄:
function example(x: string | number, y: string | boolean) {
if (x === y) {
// x 和 y 都是 string——这是它们唯一的共同类型
x.toUpperCase();
y.toLowerCase();
}
}
利用字面量做收窄也很常见:
type Status = "loading" | "success" | "error";
function handleStatus(status: Status) {
if (status === "loading") {
console.log("加载中...");
} else if (status === "success") {
console.log("成功"); // status 收窄为 "success"
} else {
console.log("失败"); // status 收窄为 "error"
}
}
switch 语句同理:
function handleStatus(status: Status) {
switch (status) {
case "loading":
return "加载中";
case "success":
return "成功";
case "error":
return "失败";
}
}
instanceof 收窄
instanceof 检查对象的构造函数,用于区分不同的类实例:
class Dog {
bark() { console.log("汪"); }
}
class Cat {
meow() { console.log("喵"); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // animal 收窄为 Dog
} else {
animal.meow(); // animal 收窄为 Cat
}
}
instanceof 只能用于类,不能用于 interface 和 type——因为它们在运行时不存在。
in 操作符收窄
in 操作符检查对象是否包含某个属性,TypeScript 根据检查结果收窄联合类型:
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim(); // animal 收窄为 Fish
} else {
animal.fly(); // animal 收窄为 Bird
}
}
in 收窄特别适合区分两个”不共享同一个字面量字段”的对象类型。比 typeof 和 instanceof 更灵活,因为不需要类或原始类型。
可选属性的影响
如果属性是可选的,它会在 in 的两个分支都出现:
type Human = { swim?: () => void; fly?: () => void };
function move(animal: Fish | Bird | Human) {
if ("swim" in animal) {
// animal: Fish | Human(Human 也可能有 swim)
} else {
// animal: Bird | Human(Human 也可能没有 swim)
}
}
控制流分析
TypeScript 不只是看 if 条件,它会分析整个执行路径。如果某个分支已经 return 了,编译器知道之后的代码绝对到不了那个分支的状态:
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
return " ".repeat(padding) + input;
}
// 能执行到这里,说明 padding 一定不是 number
// 所以 TS 自动把 padding 收窄为 string
return padding + input;
}
这叫控制流分析(Control Flow Analysis),它让编译器在你写了 return 或 throw 后自动排除已处理的分支。
小结
- 类型收窄让 TypeScript 在条件分支中自动确定更精确的类型
typeof收窄是主力,但注意typeof null === "object"的坑- truthiness 收窄用
if (x)排除null/undefined,但也会误伤空字符串和 0 equality收窄配合字面量和switch非常好用instanceof用于类实例判断in操作符通过检查属性是否存在来收窄联合类型- 控制流分析让编译器理解
return/throw后的执行路径