当前位置: > > > TypeScript - 类型断言详解(as、!、?.、??、!!)

TypeScript - 类型断言详解(as、!、?.、??、!!)

        在 TypeScript 开发过程中,有时编译器无法获取具体的类型信息,这时就需要使用类型断言Type Assertion)来告诉编译器具体的类型。本文将详细介绍 TypeScript 中常用的类型断言语法,包括 as 关键字、非空断言 !、可选链 ?.、空值合并操作符 ?? 等内容。

一、基本介绍

1,什么是类型断言

    类型断言Type Assertion)是 TypeScript 中的一种机制,允许开发者手动指定一个值的类型。当 TypeScript 编译器无法自动推断出正确的类型时,我们可以通过类型断言来明确告诉编译器具体的类型信息。

2,常用的类型断言方式

  • as 语法:使用 as 关键字进行类型断言
  • 非空断言 !:断言某个值不为 nullundefined
  • 可选链 ?.:安全访问对象属性,避免空值错误
  • 空值合并 ??:当值为 nullundefined 时提供默认值
  • 双重否定 !!:将任意值转换为布尔类型

二、类型断言 as

1,基本语法

(1)在 TypeScript 中,可以使用 as 关键字进行类型断言,语法格式为:值 as 类型。

(2)举个例子,通过 document.getElementById 获取 <img> 元素时,TypeScript 只知道该函数会返回 HTMLElement,并不知道它具体的类型:
// 类型断言 as(案例1)
const myEl = document.getElementById("my-img");

// 报错:Property 'src' does not exist on type 'HTMLElement'
myEl.src = "图片地址";

(3)这时可以使用 as 关键字进行类型断言:
提示HTMLImageElementHTMLElement 的子类型,包含了 srcalt 等图片特有的属性。
// 使用 as 关键字断言为 HTMLImageElement
const myEl = document.getElementById("my-img") as HTMLImageElement;

// 不会报错,因为明确断定 myEl 是 HTMLImageElement
myEl.src = "图片地址";

2,类型断言案例:类继承

(1)在下面的案例中,sayHello 函数接收一个 Person 类型的参数,但我们想调用 Student 类特有的方法:
// 类型断言 as(案例2)
class Person {}

class Student extends Person {
  studying() {
    console.log("正在学习");
  }
}

function sayHello(p: Person) {
  // 报错:Property 'studying' does not exist on type 'Person'
  p.studying();
}

const stu = new Student();
sayHello(stu);

(2)可以使用 as 关键字对 p 进行类型断言:
function sayHello(p: Person) {
  // 使用类型断言 as,将 p 断言为 Student
  (p as Student).studying();  // 可以调用 studying 方法
}

const stu = new Student();
sayHello(stu);  // 输出: 正在学习

3,类型断言的限制

(1)TypeScript 只允许将类型断言转换为更具体或者不太具体的类型版本,此规则可以防止不合理的强制转换:
// 不合理的类型强制转换会报错
const message = "Hello World";

// 错误:Conversion of type 'string' to type 'number' may be a mistake.
const num: number = message as number;

(2)如果确实需要进行类型转换,可以将类型先转换为 anyunknown 类型:
注意:虽然可以通过 anyunknown 绕过类型检查,但这会失去类型安全保护,可能导致运行时错误,应谨慎使用。
const message = "Hello World";

// 报错:Type 'string' is not assignable to type 'number'
// const num1: number = message;

// 方式一:先断言为 unknown,再断言为 number
const num2: number = (message as unknown) as number;

// 方式二:先断言为 any,再断言为 number
const num3: number = (message as any) as number;

console.log(num2, num3);  // 输出: Hello World Hello World

三、非空类型断言 !

1,基本语法

(1)在 JavaScript 中,经常使用 if 语句进行非空判断。而在 TypeScript 中,除了 if 语句,还可以使用非空类型断言(Non-null Assertion)进行非空判断。

(2)非空类型断言使用 ! 操作符,表示可以确定某个标识符一定是有值的,可以跳过 TypeScript 在编译阶段对它的检测。

2,使用场景

(1)下面是一个可选参数的案例,message 参数是可选的,值可能为 undefinedstring。下面代码在执行 TypeScript 编译时会报错,因为传入的 message 可能为 undefined,这时就无法访问 length 属性。
// 参数 message 是可选的,值可能为 undefined 或 string
function printMessageLength(message?: string) {
  // 编译报错:Object is possibly 'undefined'
  console.log(message.length);
}

printMessageLength("coder");

(3)有两种解决方式。第一种是使用 if 语句进行非空判断:
function printMessageLength(message?: string) {
  // 方式一:使用 if 进行非空判断
  if (message) {
    console.log(message.length);
  }
}

printMessageLength("coder");  // 输出: 5

(4)第二种是使用非空类型断言 !
注意:使用非空断言 ! 时,如果值确实为 nullundefined,运行时会报错。因为它只是跳过了编译阶段的类型检查,并没有真正进行非空判断。建议在确定值一定不为空的情况下使用。
function printMessageLength(message?: string) {
  // 方式二:使用非空类型断言 !
  // 这里断言 message 一定有值,跳过 TypeScript 编译阶段的检测
  console.log(message!.length);
}

printMessageLength("coder");  // 输出: 5

四、可选链操作符 ?.

1,基本概念

(1)可选链(Optional Chaining)是 ES11ES2020)中新增的特性,使用可选链操作符 ?. 来安全访问对象属性。

(2)可选链的作用:
  • 当对象的属性不存在时,会发生短路,直接返回 undefined
  • 当对象的属性存在时,才会继续执行
  • 避免因中间值为 nullundefined 而抛出错误
提示:虽然可选链是 ECMAScript 提出的特性,但在 TypeScript 中的使用效果更佳,可以结合类型系统实现更安全的属性访问。

2,使用示例

(1)首先定义一个复杂的对象类型和实例:
// 为对象类型起一个 Person 别名
type Person = {
  name: string;
  friend?: {
    name: string;
    age?: number;
    girlFriend?: {
      name: string;
    };
  };
};

// 定义一个对象,指定类型为 Person 类型
const info: Person = {
  name: "hangge",
  friend: {
    name: "kobe",
    girlFriend: {
      name: "lily"
    }
  }
};

(2)使用可选链访问对象的属性:
提示:可选链 ?. 与非空断言 ! 的区别:可选链在值不存在时会返回 undefined 而不报错,而非空断言在值不存在时会导致运行时错误。
// 获取 info 对象的属性,使用可选链 ?.
console.log(info.name);  // 输出: hangge

// 使用非空断言 !:当 friend 为空时,运行程序会报错
// console.log(info.friend!.name);

// 使用可选链 ?.:当 friend 不为空时,才取 age
console.log(info.friend?.age);  // 输出: undefined

// 多层可选链:当 friend、girlFriend 都不为空时,才取 name
console.log(info.friend?.girlFriend?.name);  // 输出: lily

五、!! 双重否定操作符

1,基本用法

(1)!! 操作符可以将一个其他类型的值转换成 boolean 类型,类似于 Boolean(变量) 这种函数转换的方式。
  • 简单来说:第一个 ! 将值转换为布尔值并取反,第二个 ! 再次取反,最终得到原值对应的布尔值。
const message = "Hello World";

// 方式一:以前转换 boolean 类型的方式
const flag1 = Boolean(message);
console.log(flag1);  // 输出: true

// 方式二:使用 !! 操作符转成 boolean 类型
const flag2 = !!message;
console.log(flag2);  // 输出: true

(2)!! 操作符的转换规则:
  • 假值(false0""nullundefinedNaN)转换为 false
  • 其他所有值转换为 true
// !! 操作符转换示例
console.log(!!0);          // false
console.log(!!"");        // false
console.log(!!null);      // false
console.log(!!undefined); // false
console.log(!!1);         // true
console.log(!!"hello");   // true
console.log(!!{});        // true
console.log(!![]);        // true

六、?? 空值合并操作符

1,基本概念

(1)?? 操作符是 ES11 新增的空值合并操作符(Nullish Coalescing Operator),用于判断一个值是否为 nullundefined

(2)具体来说,当左侧操作数为 nullundefined 时,返回右侧操作数;否则返回左侧操作数。

2,与 || 操作符的区别

(1)??|| 的关键区别在于判断逻辑不同:
  • || 操作符:判断左侧是否为假值(包括 false0""nullundefinedNaN
  • ?? 操作符:只判断左侧是否为 nullundefined

(2)下面通过代码对比三种判空方式:
let message: string | null = 'Hello World';
 
// 方式一:使用三元运算符判空,赋默认值
// 会判断 null、undefined、''、false 为假
const content1 = message ? message : "你好啊,小刘1";
 
// 方式二:使用 || 操作符判空,赋默认值
// 会判断 null、undefined、''、false 为假
const content2 = message || "你好啊,小刘2";
 
// 方式三:使用 ?? 操作符判空,赋默认值
// 只判断 null 或 undefined 为假
const content3 = message ?? "你好啊,小刘3";
 
console.log(content1, content2, content3);
// 输出: Hello World Hello World Hello World

(3)当值为空字符串或 0 时的区别:
提示:在需要保留 0 或空字符串 "" 作为有效值的场景下,应该使用 ?? 而不是 ||
// 当值为空字符串时
let str: string = "";

console.log(str || "默认值");   // 输出: 默认值(空字符串被视为假值)
console.log(str ?? "默认值");  // 输出: ''(空字符串不是 null 或 undefined)

// 当值为 0 时
let num: number = 0;

console.log(num || 100);   // 输出: 100(0 被视为假值)
console.log(num ?? 100);   // 输出: 0(0 不是 null 或 undefined)
评论0