类型断言、类型守卫与二者对比
前两篇为专题,第三篇为对照表与总结。
第一部分:类型断言
类型断言是手动指定变量类型的语法,仅作用于编译阶段,不改变运行时类型
作用
核心作用是让你手动告诉编译器某个值的具体类型,覆盖它的默认类型推断。
语法
- 语法一:尖括号 语法(<类型>值)
这是早期的语法形式,在 JSX 中会与标签语法冲突(因此 JSX 中不推荐使用)
ts
// 示例:把一个不确定类型的变量断言为字符串
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
console.log(strLength); // 输出:16- 语法二:as 语法(值 as 类型)
官方推荐的语法,兼容 JSX 场景,是目前最常用的写法
ts
// 示例:把一个不确定类型的变量断言为字符串
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
console.log(strLength); // 输出:16as const常量断言:将字面量收窄为只读字面量类型,例如const x = [1, 2] as const。非空断言
!(慎用):写在表达式后缀,如document.getElementById('id')!,表示断言此处不是null/undefined;不是value! as Type这种写法。
ts
// 非空断言示例(确定 DOM 存在时)
const el = document.getElementById('app')!;
el.innerHTML = 'ok';shell
# const 断言(as const)
# 将值的类型收窄为字面量联合,并把对象/元组标为 readonly
语法:值 as const使用场景
- 场景一:DOM 元素断言(最常用的实战场景)
ts
// 原因:TS 对 DOM 元素的默认类型是宽泛的(如 HTMLElement/null),需要断言为具体的元素类型(如 HTMLInputElement)。
// 获取输入框元素,默认类型是 HTMLElement | null
const input = document.getElementById("username");
// 断言为 HTMLInputElement(排除 null 需额外判断)(相比非空断言更安全)
if (input) {
console.log((input as HTMLInputElement).value); // 访问 input 的 value 属性
}
// 简化写法(结合非空断言 !,仅确定元素一定存在时使用)
const input2 = document.getElementById("username")! as HTMLInputElement;
console.log(input2.value);- 场景二:收窄类型
ts
// 原因:当变量类型是宽泛的(如 any/unknown/ 联合类型),需要缩小为更具体的类型时使用。
// 1. unknown 类型断言为具体类型
function getLength(value: unknown): number {
// 先断言为 string,再访问 length 属性
if (typeof value === "string") {
return (value as string).length;
}
// 断言为数组,访问 length 属性
if (Array.isArray(value)) {
return (value as any[]).length;
}
return 0;
}
console.log(getLength("hello")); // 5
console.log(getLength([1, 2, 3])); // 3
// 2. 联合类型断言为其中一种类型
type MyType = string | number;
function printStr(str: MyType) {
// 断言为 string,调用字符串方法
console.log((str as string).toUpperCase());
}
printStr("typescript"); // TYPESCRIPT- 场景三:“双重断言”(谨慎使用)
ts
// 案例:当需要跨类型断言(如基本类型 → 对象类型),TS 会禁止直接断言,此时需要通过 any/unknown 作为中间层(双重断言)。
// 原因:容易掩盖真实的类型错误,非必要不使用。
/// 直接断言会报错:类型 'string' 不能转换为类型 'number[]'
// let numArr = "123" as number[];
// 双重断言(先断言为 any,再断言为目标类型)
let numArr = "123" as any as number[];
console.log(numArr); // 输出:"123"(实际类型仍为字符串)- 场景四:“常量断言”
ts
// 普通对象:属性可修改,类型是 { name: string; age: number }
const user = { name: "张三", age: 20 };
user.age = 21; // 可以修改
// const 断言:对象变为 readonly,属性类型固化为字面量
const userConst = { name: "张三", age: 20 } as const;
// userConst.age = 21; // 报错:无法分配到 "age",因为它是只读属性
// 类型:readonly { readonly name: "张三"; readonly age: 20; }
type UserConstType = typeof userConst;注意事项
类型断言是 TS 编译时的类型提示,不影响运行时逻辑
类型断言不是类型转换:它只影响编译阶段的类型检查,运行时不会做任何类型转换。如果断言错误,运行时可能报错
ts
let num: any = 123;
// 错误断言:num 实际是数字,运行时调用 split 会报错
const str = num as string;
str.split(""); // 运行时错误:num.split is not a function- 断言的限制:两个类型之间没有直接继承或兼容关系时,使用断言会报错,需要双重断言
shell
# 解决方案:双重断言(如果非要强制断言(不推荐),可以通过 any 中转)
可以先断言成 any 或者 unknown,这样可以绕过类型检查,再断言成目标类型
语法:变量值 as any as 目标类型第二部分:类型守卫
类型守卫是一段运行时检查的代码
它的作用是在特定代码块内「缩小」变量的类型范围(类型窄化)
让 TypeScript 编译器能够精确推断出变量的具体类型,从而避免类型错误,同时获得更好的类型提示。
没有类型守卫时,TS 只知道变量是宽泛的联合类型;有了类型守卫后,TS 能确定变量在当前分支下的具体类型。
应用场景
- 处理联合类型:这是最核心的场景,避免在联合类型上调用不存在的方法 / 属性;
- 处理可选值 / 空值:结合 typeof value !== 'undefined' 或 value !== null 窄化非空类型;
- 处理接口 / 类的多态:区分不同接口 / 类的实例,调用各自的方法;
- 运行时类型校验:弥补 TypeScript 编译后类型擦除的问题,确保运行时数据符合预期。
种类
基础场景用 typeof / instanceof / in,复杂场景用自定义类型守卫(is 关键字)
typeof检查基础类型:适用于检查原始类型(string/number/boolean/symbol/undefined/bigint/function),是最基础的类型守卫。instanceof检查类/实例:适用于检查对象是否是某个类的实例(包括内置类如Array/Date,或自定义类),依赖原型链判断。- in 检查对象的属性:适用于检查对象是否包含某个特定属性,从而窄化联合类型中对象的类型。
- 自定义类型守卫:通过 is 关键字明确窄化后的类型。
- 字面量类型守卫:const 断言 / 等值判断,适用于字面量类型(如 'success' | 'error' | 'loading'),通过等值判断(===/!==)窄化类型。
typeof 检查基础类型
ts
function printValue(value: string | number | boolean) {
// 类型守卫:检查 value 是否为 string 类型
if (typeof value === 'string') {
// 此处 TS 能确定 value 是 string 类型,可安全调用 string 方法
console.log('字符串长度:', value.length);
}
// 类型守卫:检查 value 是否为 number 类型
else if (typeof value === 'number') {
// 此处 TS 能确定 value 是 number 类型,可安全进行数值运算
console.log('数值翻倍:', value * 2);
}
// 剩余分支自动推断为 boolean 类型
else {
console.log('布尔值取反:', !value);
}
}
// 测试
printValue('Hello'); // 输出:字符串长度: 5
printValue(100); // 输出:数值翻倍: 200
printValue(true); // 输出:布尔值取反: falseinstanceof 检查类/实例
ts
class Dog {
bark() {
console.log('汪汪汪');
}
}
class Cat {
meow() {
console.log('喵喵喵');
}
}
function petSay(pet: Dog | Cat) {
// 类型守卫:检查 pet 是否是 Dog 的实例
if (pet instanceof Dog) {
pet.bark(); // 此处 TS 确定 pet 是 Dog 类型
} else {
pet.meow(); // 此处 TS 确定 pet 是 Cat 类型
}
}
// 测试
petSay(new Dog()); // 输出:汪汪汪
petSay(new Cat()); // 输出:喵喵喵in 检查对象的属性
ts
interface User {
name: string;
login: () => void;
}
interface Guest {
name: string;
register: () => void;
}
function handleUser(user: User | Guest) {
// 类型守卫:检查 user 是否包含 login 属性
if ('login' in user) {
user.login(); // 此处 TS 确定 user 是 User 类型
} else {
user.register(); // 此处 TS 确定 user 是 Guest 类型
}
}
// 测试
handleUser({ name: '张三', login: () => console.log('登录成功') }); // 输出:登录成功
handleUser({ name: '游客', register: () => console.log('注册成功') }); // 输出:注册成功自定义类型守卫
ts
// 定义联合类型
type NumArray = number[];
type StrArray = string[];
type MixedArray = NumArray | StrArray;
// 自定义类型守卫:判断数组是否为数字数组
function isNumArray(arr: MixedArray): arr is NumArray {
// 运行时检查:数组中所有元素都是数字
return arr.every((item) => typeof item === 'number');
}
function sumArray(arr: MixedArray) {
// 使用自定义类型守卫
if (isNumArray(arr)) {
// 此处 TS 确定 arr 是 NumArray 类型,可安全求和
return arr.reduce((sum, num) => sum + num, 0);
} else {
// 此处 TS 确定 arr 是 StrArray 类型,拼接字符串
return arr.join(', ');
}
}
// 测试
console.log(sumArray([1, 2, 3])); // 输出:6
console.log(sumArray(['a', 'b', 'c'])); // 输出:a, b, c字面量类型守卫
ts
type Status = 'success' | 'error' | 'loading';
function handleStatus(status: Status) {
// 类型守卫:等值判断
if (status === 'success') {
console.log('操作成功'); // 此处 status 是 'success' 字面量类型
} else if (status === 'error') {
console.log('操作失败'); // 此处 status 是 'error' 字面量类型
} else {
console.log('加载中...'); // 此处 status 是 'loading' 字面量类型
}
}
// 测试
handleStatus('loading'); // 输出:加载中...第三部分:断言与守卫对比
对比
shell
维度 类型断言 类型守卫
本质 手动覆盖类型(编译期语法) 运行时检查类型(代码逻辑)
类型安全 不安全(编译器信任你的断言,无校验) 安全(基于运行时检查缩小类型范围)
运行时影响 无(仅编译期生效) 有(执行检查逻辑)
类型推导 强制指定类型,无自动推导 编译器自动推导窄类型
适用场景 明确知道类型,且无法通过逻辑推导 需要动态判断类型,保证类型安全总结
- 类型断言是 “硬指定” 类型,仅编译期生效,适合你100% 确定类型的场景(如 DOM 元素获取),但要谨慎使用,避免运行时错误
- 类型守卫是 “动态推导” 类型,通过运行时检查缩小类型范围,是保证类型安全的首选方式,适合需要分支判断类型的场景
- 核心原则:能使用类型守卫的场景,尽量不用类型断言;只有当你明确知道类型且无法通过逻辑推导时,才考虑类型断言
