TypeScript - 类型缩小详解(typeof、instanceof、in、平等缩小、自定义类型保护)
作者:hangge | 2026-08-07 08:55
在 TypeScript 中,缩小类型的过程被称为类型缩小(Type Narrowing)。当我们使用条件语句检查值的类型时,TypeScript 能够在特定的执行路径中缩小变量的类型,使其比声明时更为精确。本文将详细介绍 TypeScript 中常用的类型缩小方式,包括 typeof、instanceof、in 操作符、平等缩小以及自定义类型保护等内容。






一、基本介绍
1,什么是类型缩小
(1)类型缩小(Type Narrowing)是 TypeScript 中的一个重要概念。例如,我们可以使用类似
typeof padding === "number" 的条件语句改变 TypeScript 的执行路径,在特定的执行路径中缩小变量的类型。提示:我们编写的
typeof padding === "number" 这种缩小类型的代码就是一种类型保护(Type Guard)代码。类型保护可以帮助我们在编译时获得更精确的类型信息。(2)TypeScript 中常见的类型保护方式:
- typeof:检查值的类型,如
typeof x === "number" - instanceof:检查值是否为某个类的实例,如
d instanceof Date - in:检查对象是否具有某个属性,如
"name" in obj - 平等缩小:使用
===、!==、switch等进行类型缩小 - 自定义类型保护:使用类型谓词
is定义类型保护函数
二、typeof 类型保护
1,基本用法
(1)JavaScript 支持 typeof 运算符,可以使用该运算符来检查值的类型。在 TypeScript 中,可以通过检查值的类型来缩小不同分支中的类型。
(2)下面是一个使用 typeof 实现类型缩小的示例。可以看到,这里使用 typeof 检查 id 的类型。如果 id 的类型是字符串,则调用 toUpperCase 方法。通过使用 typeof 进行类型判断,我们将联合类型的 id 缩小为更具体的字符串类型。
type IDType = number | string;
function printID(id: IDType) {
// 使用 typeof 实现类型缩小,将 id 从联合类型缩小为 string 类型
if (typeof id === 'string') {
// 在这个分支中,TypeScript 知道 id 是 string 类型
console.log(id.toUpperCase());
} else {
// 在这个分支中,TypeScript 知道 id 是 number 类型
console.log(id);
}
}
printID("hello"); // 输出: HELLO
printID(123); // 输出: 123

2,typeof 支持的类型
typeof 类型保护只有特定的形式能被 TypeScript 识别:
注意:typeof null 返回 "object",这是 JavaScript 的历史遗留问题。如果需要检查 null,应该使用
x === null 而不是 typeof。// TypeScript 能识别的 typeof 类型保护形式
if (typeof x === "number") { /* x 是 number 类型 */ }
if (typeof x === "string") { /* x 是 string 类型 */ }
if (typeof x === "boolean") { /* x 是 boolean 类型 */ }
if (typeof x === "symbol") { /* x 是 symbol 类型 */ }
if (typeof x === "undefined") { /* x 是 undefined 类型 */ }
if (typeof x === "function") { /* x 是 Function 类型 */ }
if (typeof x === "object") { /* x 是 object 类型(注意:null 也是 object) */ }
三、平等缩小
1,基本概念
(1)平等缩小(Equality Narrowing)是指使用 switch、===、!==、==、!= 等运算符来表达相等性,进而实现类型缩小。
(2)下面是一个使用平等缩小的示例:
type Direction = "left" | "right" | "top" | "bottom";
function printDirection(direction: Direction) {
// 使用 if 判断,缩小类型
if (direction === 'left') {
// 在这个分支中,direction 被缩小为 "left" 字面量类型
console.log("向左移动");
} else if (direction === 'right') {
console.log("向右移动");
} else {
// 在这个分支中,direction 只能是 "top" 或 "bottom"
console.log("垂直移动");
}
}
printDirection("left"); // 输出: 向左移动

2,switch 语句缩小
switch 语句也是实现平等缩小的常用方式,特别适合处理多个分支的情况:
提示:使用 switch 语句进行类型缩小特别适合处理字面量联合类型,可以让代码更加清晰易读。
type Direction = "left" | "right" | "top" | "bottom";
function printDirection(direction: Direction) {
// 使用 switch 判断,缩小类型
switch (direction) {
case 'left':
console.log("向左移动");
break;
case 'right':
console.log("向右移动");
break;
case 'top':
console.log("向上移动");
break;
case 'bottom':
console.log("向下移动");
break;
}
}
printDirection("top"); // 输出: 向上移动

四、instanceof 类型保护
1,基本用法
(1)JavaScript 中有一个 instanceof 运算符,用于检查一个值是否为另一个值的实例。TypeScript 会自动缩小由 instanceof 保护的分支中的类型。
提示:instanceof 只能用于类(class)类型的检查,不能用于接口(interface)类型,因为接口在编译后会被移除。
(2)下面演示 instanceof 运算符的使用。可以看到,在 work 函数中通过判断变量 p 是否为 Student 类型的实例,将联合类型的 p 缩小为更具体的 Student 类型。
class Student {
studying() {
console.log("正在学习");
}
}
class Teacher {
teaching() {
console.log("正在教学");
}
}
function work(p: Student | Teacher) {
// 判断 p 是否为 Student 类型的实例,进行类型缩小
if (p instanceof Student) {
// 在这个分支中,p 被缩小为 Student 类型
p.studying();
} else {
// 在这个分支中,p 被缩小为 Teacher 类型
p.teaching();
}
}
const stu = new Student();
work(stu); // 输出: 正在学习

五、in 操作符类型保护
1,基本用法
(1)JavaScript 中有一个 in 运算符,用于判断对象是否具有指定名称的属性。如果指定的属性存在于该对象或其原型链中,那么 in 运算符将返回 true。
(2)TypeScript 中可以使用 in 操作符来缩小对象类型。通过下面样例可以看到,在 walk 函数中通过判断 swimming 是否为 animal 对象中的属性,将 animal 的联合类型缩小为更具体的 Fish 类型。
// 定义 Fish 和 Dog 为对象类型
type Fish = {
swimming: () => void; // swimming 是函数类型
};
type Dog = {
running: () => void; // running 是函数类型
};
function walk(animal: Fish | Dog) {
// 判断 swimming 是否为 animal 对象中的属性,进行类型缩小
if ('swimming' in animal) {
// 在这个分支中,animal 被缩小为 Fish 类型
animal.swimming();
} else {
// 在这个分支中,animal 被缩小为 Dog 类型
animal.running();
}
}
// 创建 fish 对象,该对象的类型为 Fish 类型
const fish: Fish = {
swimming() {
console.log("swimming");
}
};
walk(fish); // 输出: swimming

六、自定义类型保护
1,类型谓词 is
(1)有时候我们需要更复杂的类型检查逻辑,这时可以定义自定义类型保护。自定义类型保护使用类型谓词(Type Predicate)来指定返回值的类型。
(2)类型谓词的语法格式为:
parameterName is Type,其中 parameterName 必须是当前函数签名里的一个参数名。下面是一个简单的样例:提示:每当使用变量调用类型保护函数时,TypeScript 会将该变量缩小为类型谓词指定的具体类型。
type Fish = {
swimming: () => void;
};
type Bird = {
flying: () => void;
};
// 定义类型保护函数,返回类型是类型谓词
function isFish(pet: Fish | Bird): pet is Fish {
return 'swimming' in pet;
}
function move(pet: Fish | Bird) {
if (isFish(pet)) {
// TypeScript 知道在这个分支中 pet 是 Fish 类型
pet.swimming();
} else {
// TypeScript 知道在这个分支中 pet 是 Bird 类型
pet.flying();
}
}
const fish: Fish = {
swimming() {
console.log("鱼在游泳");
}
};
move(fish); // 输出: 鱼在游泳

2,类型保护函数的优势
(1)使用自定义类型保护的优势:
- 代码复用:可以将复杂的类型检查逻辑封装成函数,在多处使用
- 类型安全:TypeScript 会自动根据返回值缩小类型
- 可读性:函数名可以清晰地表达类型检查的意图
(2)下面是一个更复杂的自定义类型保护示例:
interface Admin {
name: string;
privileges: string[]; // 管理员特有的权限列表
}
interface User {
name: string;
startDate: Date; // 普通用户特有的注册日期
}
// 自定义类型保护函数
function isAdmin(account: Admin | User): account is Admin {
return 'privileges' in account;
}
function printAccountInfo(account: Admin | User) {
console.log("用户名:", account.name);
if (isAdmin(account)) {
console.log("权限列表:", account.privileges);
} else {
console.log("注册日期:", account.startDate);
}
}
const admin: Admin = {
name: "管理员",
privileges: ["read", "write", "delete"]
};
printAccountInfo(admin);
// 输出:
// 用户名: 管理员
// 权限列表: ["read", "write", "delete"]
全部评论(0)