5. TypeScript 类型缩小
在 TypeScript 中,类型缩小(Narrowing)是指根据特定条件将变量的类型细化为更具体的过程。它帮助开发者编写更精确、更准确的代码,确保变量在运行时只以符合其类型的方式进行处理。
一、instanceof
缩小类型
TypeScript 中的 instanceof
运算符用于类型缩小,它允许我们检查一个对象是否是特定类或构造函数的实例。当我们在条件语句中使用 instanceof
时,TypeScript 会根据检查结果缩小变量的类型。
(一) 语法
if (objectToCheck instanceof ConstructorFunction) {// 如果 objectToCheck 是 ConstructorFunction 的实例,则执行此代码块
}
其中:
- objectToCheck:是你想要检查的对象,用于判断它是否是某个特定类或构造函数的实例。
- ConstructorFunction:是你要用来进行检查的构造函数或类构造器。如果 objectToCheck 是ConstructorFunction 的实例,该条件将为 true,TypeScript 会相应地缩小 objectToCheck 的类型。
- instanceof:是一个用于类型缩小的关键字,它允许你检查一个对象是否是特定类或构造函数的实例。
(二) 用法举例
1. 例一
在这个示例中,我们有两个类:Car 和 Bicycle,它们各自拥有自己的方法(start 和 pedal)。
startVehicle 函数接受一个名为 vehicle 的参数,该参数可以是 Car 或 Bicycle。
在函数内部,我们使用 instanceof 来检查 vehicle 的类型。
如果 vehicle 是 Car 的实例,类型将被缩小为 Car,从而可以调用 start 方法。
如果 vehicle 是 Bicycle 的实例,类型将被缩小为 Bicycle,从而可以调用 pedal 方法。
class Car {start() {console.log("Car started");}
}class Bicycle {pedal() {console.log("Bicycle pedaling");}
}function startVehicle(vehicle: Car | Bicycle) {if (vehicle instanceof Car) {// 在此代码块中,'vehicle' 的类型被缩小为 'Car'vehicle.start();} else {// 在此代码块中,'vehicle' 的类型被缩小为 'Bicycle'vehicle.pedal();}
}const myCar = new Car();
const myBicycle = new Bicycle();startVehicle(myCar);
startVehicle(myBicycle);
输出:
2. 例二
在这个示例中,我们有一个类的继承体系:Animal、Dog 和 Cat。
animalSound 函数接受一个 Animal 类型的参数。
在函数内部,我们使用 instanceof 检查 animal 是否是 Dog 或 Cat 的实例。
如果是 Dog,animal 的类型被缩小为 Dog,我们可以调用其 speak 方法发出狗叫。
如果是 Cat,animal 的类型被缩小为 Cat,我们可以调用其 speak 方法发出猫叫。
否则,如果既不是 Dog 也不是 Cat,它仍然是 Animal 类型,我们调用通用的 speak 方法。
class Animal {speak() {console.log("Animal makes a sound");}
}class Dog extends Animal {speak() {console.log("Dog barks");}
}class Cat extends Animal {speak() {console.log("Cat meows");}
}function animalSound(animal: Animal) {if (animal instanceof Dog) {// 在此代码块中,'animal' 的类型被缩小为 'Dog'animal.speak(); // 调用 Dog 的 speak 方法} else if (animal instanceof Cat) {// 在此代码块中,'animal' 的类型被缩小为 'Cat'animal.speak(); // 调用 Cat 的 speak 方法} else {// 在此代码块中,'animal' 仍然是 'Animal' 类型animal.speak(); // 调用 Animal 的 speak 方法}
}const dog = new Dog();
const cat = new Cat();
const genericAnimal = new Animal();animalSound(dog);
animalSound(cat);
animalSound(genericAnimal);
输出:
二、typeof
类型保护
在本节中,我们将学习关于 typeof
类型保护(type guards)中的类型缩小。TypeScript 是一种流行的编程语言,用于构建可扩展且健壮的应用程序。在 TypeScript 中,typeof
类型保护允许你根据 typeof
运算符的结果缩小变量的类型。
这在处理诸如 string、number、boolean、symbol 和 undefined 等原始类型时特别有用,也可以用于检查一个变量是否是函数(function)或对象(object)。
以下是 typeof
运算符可以识别的类型:
- string
- number
- bigint
- boolean
- symbol
- undefined
- object
- function
(一) 语法
if (typeof variable === 'type') {// 当变量匹配指定类型时执行的代码
}
其中:
typeof
:这是 TypeScript 中用于检查变量类型的运算符。variable
:是你想要检查其类型的变量。'type'
:是表示你期望检查的类型的字符串字面量。
(二) 用法举例
1. 例一
你可以使用 typeof
来检查一个变量是否是字符串类型。
const p = '2025 TypeScript Learning Guide';
if (typeof p === 'string') {// 在这里,TypeScript 知道 'p' 是一个字符串。console.log(p.toUpperCase());
}
输出:
2025 TYPESCRIPT LEARNING GUIDE
2. 例二
你可以使用 typeof
来检查一个变量是否是数字类型。
const age = 30;
if (typeof age === 'number') {// TypeScript 知道 'age' 是一个数字。console.log(age * 2);
}
输出:
60
3. 例三
你可以使用 typeof
来检查一个变量是否是布尔类型(boolean)。
const isActive = true;
if (typeof isActive === 'boolean') {// TypeScript 知道 'isActive' 是一个布尔值。console.log(isActive);
}
输出:
false
4. 例四
你可以使用 typeof
来检查一个变量是否是符号类型(symbol)。
const uniqueSymbol = Symbol('unique');
if (typeof uniqueSymbol === 'symbol') {// TypeScript 知道 'uniqueSymbol' 是 symbol 类型。console.log(uniqueSymbol.description);
}
输出:
unique
5. 例五
你可以使用 typeof
来检查一个变量是否是 undefined
类型。
let someValue;
if (typeof someValue === 'undefined') {// TypeScript 知道 // 'someValue' 是 undefined 类型。console.log('值是未定义的。');
}
输出:
值是未定义的。
6. 例六
这个例子检查了变量的类型是否为对象(typeof 为 "object"),但它不能区分不同的对象类型。它只能告诉你该变量不是其他基本类型之一。要更精确地判断对象类型,可能需要使用其他类型保护手段,比如 instanceof
或者检查对象是否存在某些特定属性。
const person = {name: 'FelixLu',age: 30,
};if (typeof person === 'object') {console.log('person 是一个对象');// 在这个代码块内,你可以安全地访问 person 的属性console.log(`名字: ${person.name}, 年龄: ${person.age}`);
}
输出:
person 是一个对象
名字: FelixLu, 年龄: 30
7. 例七
你可以使用 typeof
来检查一个变量是否是函数。
function greet() {console.log('Hello!');
}if (typeof greet === 'function') {// TypeScript 知道 'greet' 是一个函数。greet();
}
输出:
Hello!
三、相等缩小类型
本节将介绍 TypeScript 中的“相等缩小类型”(Equality Narrowing Type)。
(一) 语法
TypeScript 是一种用于构建可扩展且健壮应用程序的流行编程语言。在 TypeScript 中,“相等缩小”指的是根据相等性检查或比较来缩小变量类型的过程。TypeScript 可以通过使用相等性检查符号(===、==、!==、!=)或比较运算符(<、>、<=、>=)来推断更具体的类型,同时也可以利用 switch 语句实现类型缩小。
JavaScript 中较宽松的相等检查(== 和 !=)也能被 TypeScript 正确缩小类型。如果你不熟悉,检查某个值是否 == null 实际上不仅仅是判断它是否为 null,还会判断它是否可能是 undefined。同样,== undefined 检查的也是值是否为 null 或 undefined。
(二) 用法举例
1. 例一
在这个例子中,我们有一个 processValue
函数,参数 value
的类型是 number | string
。在函数内部,我们使用 typeof value === 'number'
来检查 value
是否为数字类型。如果是,TypeScript 会在 if
代码块内将 value
的类型缩小为 number
,因此可以安全地对它执行数值操作。在 else
代码块中,TypeScript 将 value
的类型缩小为 string
,因为它知道 value
不是数字(由 if
代码块的检查排除),这样你就可以安全地调用 toUpperCase()
方法。
function processValue(value: number | string): void {if (typeof value === 'number') {// 在这个代码块中,TypeScript// 将 value 的类型缩小为 'number'// 它知道 value 是数字类型console.log(value + 10);} else {// 在这个代码块中,TypeScript// 将 value 的类型缩小为 'string'// 它知道 value 是字符串类型console.log(value.toUpperCase());}
}// 使用示例:
processValue(5);
processValue('TypeScript');
输出:
2. 例二
这段示例中,我们定义了一个 processArray
函数,接受一个参数 arr
,其类型为 number[]
或 string[]
,表示它可以处理数字数组或字符串数组。函数内部通过 Array.isArray(arr)
检查 arr
是否为数组。如果是,TypeScript 会在该 if
代码块中将 arr
的类型缩小为 number[]
或 string[]
。
在 forEach
循环中,TypeScript 进一步根据数组元素的类型缩小 item
的类型。如果 item
是数字,TypeScript 会识别它为数字类型;如果是字符串,则识别为字符串类型。这样,我们就可以在循环内安全地执行针对数字或字符串的特定操作,而不会引发类型错误。
当我们用 numberArray
和 stringArray
调用 processArray
时,TypeScript 会正确地缩小类型,并在函数内进行类型安全的操作。
function processArray(arr: number[] | string[]): void {if (Array.isArray(arr)) {// TypeScript 在此代码块中将 arr 的类型缩小为// 'number[]' 或 'string[]'arr.forEach((item) => {if (typeof item === "number") {// TypeScript 在这里将 item 的类型缩小为 'number'console.log(`Number: ${item}`);} else {// TypeScript 在这里将 item 的类型缩小为 'string'console.log(`String: ${item}`);}});}
}const numberArray: number[] = [1, 2, 3];
const stringArray: string[] = ["Typescript", "Java", "React"];processArray(numberArray);
processArray(stringArray);
输出:
3. 例三
在这个例子中,printPersonInfo 函数接受一个类型为 Person 的参数 person。Person 是一个接口,包含 name 属性和一个可选的 age 属性。函数内部使用 in 操作符检查 person 对象中是否存在 age 属性。如果 age 属性存在('age' in person 计算结果为 true),TypeScript 会在 if 代码块中缩小 person 的类型,使其包含 age 属性。这样我们就可以安全地访问 person.age。如果 age 属性不存在('age' in person 计算结果为 false),TypeScript 会在 else 代码块中缩小类型,排除 age 属性。
interface Person {name: string;age?: number;
}function printPersonInfo(person: Person) {if ('age' in person) {// 在此代码块中,'person' 被缩小为包含 'age' 属性的类型console.log(`Name: ${person.name}, Age: ${person.age}`);} else {// 在此代码块中,'person' 被缩小为不包含 'age' 属性的类型console.log(`Name: ${person.name}, Age not provided`);}
}const p1: Person = { name: 'FelixLu', age: 30 };
const p2: Person = { name: 'Felix' };printPersonInfo(p1);
printPersonInfo(p2);
输出:
四、真值收窄类型
在本节中,我们将学习 TypeScript 中的真值收窄类型(Truthiness Narrowing Type)。
TypeScript 是一种流行的编程语言,常用于构建可扩展且健壮的应用程序。在 TypeScript 中,“真值收窄”是一种根据变量在条件语句中的真假值来缩小其类型的机制。这是一种类型推断机制,利用了 JavaScript 中的“真值”(truthy)和“假值”(falsy)来确定变量可能的类型。
真值(Truthy Values):
在 JavaScript 中,被认为是“真值”的值在布尔上下文中会被当作 true
来处理。这些值包括:
- 非空字符串(如
"hello"
、"true"
等) - 非零数字(如
1
、-42
等) - 对象(包括数组和函数)
true
- 拥有
valueOf
方法并返回true
的用户自定义类的实例
假值(Falsy Values):
相反,被认为是“假值”的值在布尔上下文中会被当作 false
来处理。这些值包括:
- 空字符串(
""
) - 数字 0(
0
) null
undefined
false
0n
(BigInt 类型的零)NaN
(不是一个数字)
(一) 语法
if (condition) {// 在这个代码块中,TypeScript 会收窄变量的类型。
} else {// 在这个代码块中,TypeScript 会根据条件得知变量的不同类型信息。
}
其中:
- condition:这是你希望检查其“真值”或“假值”的表达式或变量。
- if 代码块中:TypeScript 会根据条件的真值情况收窄变量的类型。如果条件被认为是 true,TypeScript 会相应地收窄变量的类型。
- else 代码块中(可选):由于条件被判定为 false,TypeScript 会对变量有不同的类型推断,与 if 块中相反。
(二) 用法举例
1. 例一
在 if
代码块中,TypeScript 将 name
的类型收窄为 string
,因为它检查了 name
是否为真值(即不是 null
或 undefined
)。
在 else
代码块中,TypeScript 理解 name
只能是 null
或 undefined
,因为它已经在 if
中检查过其为真值。
function greet(name: string | null) {if (name) {// 在这个代码块中,TypeScript 知道 // 'name' 是一个非 null 的字符串。console.log(`Hello, ${name.toUpperCase()}!`);} else {// 在这个代码块中,TypeScript 知道 // 'name' 要么是 null,要么是 undefined。console.log("Hello, TypeScript!");}
}greet("TypeScript");
greet(null);
输出:
2. 例二
在这个示例中,printType
函数接收一个参数 value
。在函数内部,我们使用 typeof
来检查 value
的类型。根据不同的类型,TypeScript 会在每个 if
代码块中收窄 value
的类型。这使我们能够安全地执行与类型相关的操作。
function printType(value: any) {if (typeof value === 'string') {console.log("它是一个字符串:", value.toUpperCase());} else if (typeof value === 'number') {console.log("它是一个数字:", value.toFixed(2));} else {console.log("它是其他类型:", value);}
}printType("Hello, TypeScript!");
printType(42);
printType(true);
输出:
五、类型谓词
在本文中,我们将学习 TypeScript 中的类型谓词。TypeScript 是一种静态类型编程语言,提供了许多功能来使你的代码更加高效和健壮。TypeScript 中的类型谓词是返回布尔值的函数,用于缩小变量的类型范围。它们主要用于条件语句块中,检查变量是否属于特定类型,然后据此执行相应的操作。类型谓词可以使用 TypeScript 中的关键字 “is” 来定义。
(一) 语法
function is<TypeName>(variable: any): variable is TypeName {// 返回布尔值
}
在上面的语法中,TypeName
是你想要检查的类型名称,variable
是你想要缩小类型范围的变量。is
关键字表示该函数返回一个布尔值,而 variable is TypeName
告诉 TypeScript,如果函数返回 true,那么该变量就是指定的类型。
(二) 用法举例
1. 使用原始类型的类型谓词
在这个示例中,我们将了解类型谓词如何与原始类型一起工作。假设我们有一个接受类型为 any
的变量的函数,我们想检查它是否是字符串。我们可以这样定义一个类型谓词函数:通过使用 JavaScript 中的 typeof
操作符来判断变量是否为字符串。如果变量是字符串,函数将返回 true
;否则返回 false
。
现在,我们在条件语句中使用这个类型谓词函数,通过 isString
函数检查变量是否是字符串,并打印我们选择的结果。
let variable: any = 'hello';if (isString(variable)) {console.log("给定的变量是字符串类型");
}function isString(variable: any): variable is string {return typeof variable === 'string';
}
输出:
2. 带自定义类型的类型谓词
下面通过另一个例子来理解类型谓词如何与自定义类型配合使用。假设我们有一个表示“Person”对象的接口。接着,我们定义一个类型谓词函数,用于检查某个变量是否为“Person”类型。函数通过使用 in
操作符检查变量是否包含 name
、age
和 address
属性。如果变量是“Person”类型,函数返回 true
;否则返回 false
。然后我们可以在条件语句中使用这个类型谓词函数。如果函数返回 true
,我们就可以安全地访问“address”对象中的 city
属性,因为此时我们已经知道变量的类型是“Person”。
interface Person {name: string;age: number;address: {street: string;city: string;zip: number;};
}
// 定义类型谓词函数,判断变量是否为 Person 类型
function isPerson(variable: any): variable is Person {return (typeof variable === "object" && // 判断变量是对象variable !== null && // 且不为 null"name" in variable && // 并且拥有 name 属性"age" in variable && // 拥有 age 属性"address" in variable // 拥有 address 属性);
}let variable: any = {name: "John",age: 30,address: {street: "皂君庙甲10号",city: "北京",zip: 10001},
};// 如果变量是 Person 类型,则安全访问 address.city 属性
if (isPerson(variable)) {console.log(variable.address.city);
}
输出: