类型断言用于手动指定一个值的类型。

一、语法

值 as 类型

二、用途

2.1 将一个联合类型断言为其中一个类型

interface Cat {
name:string;
run():void;
}
interface Fish {
name:string;
swim():void;
}
function getName(animal:Cat|Fish):string{
return animal.name;
}

只能访问联合属性中共有的属性和方法——​​name​​。

如果在不确定类型的时候就想访问一个类型确定的属性和方法,就需要【断言】:

function isFish(animal:Cat|Fish):boolean{
return (typeof (animal as Fish).swim) === "function";
}

将变量​​animal​​​断言为​​Fish​​​类型,那么访问其​​swim​​属性,就可以通过编译器的检查。

类型断言相当于欺骗编译器,编译的时候不报错,不代表运行的时候不报错。

const tom:Cat = {
name:"cat",
run(){
console.log("running");
}
}
function swim(animal:Cat|Fish){
(animal as Fish).swim();
}
swim(tom);

编译结果:

var tom = {
name: "cat",
run: function () {
console.log("running");
}
};
function swim(animal) {
animal.swim();
}
swim(tom);
//TypeError: animal.swim is not a function

2.2 将一个父类断言为更加具体的子类

class ApiError extends Error{
code:number = 0;
}
class HttpError extends Error{
statusCode:number = 200;
}
function isApiError(error:Error){
return (typeof (error as ApiError).code === "number");
}

父类​​Error​​​并没有​​code​​​属性,会直接报错,所以需要将其断言为子类​​ApiError​​。

上例用instanceof判断似乎更为合适,如果​​ApiError​​​、​​HttpError​​为接口,那么就只能用断言来判断了:

interface ApiError extends Error{
code:number;
}
interface HttpError extends Error{
statusCode:number;
}

2.3 将任何一个类型断言为any

window.a = "hello world";
//Property 'a' does not exist on type 'Window & typeof globalThis'

只是想给​​window​​​添加一个属性,但因为​​window​​​上并没有​​a​​​属性,所以报错。此时,就需要将​​window​​​断言为​​any​​:

(window as any).a = "hello world";

不能滥用​​as any​​,也不能完全否定它的作用,需要在类型的严格性和开发的便利性之间掌握平衡。

2.4 将any断言为一个具体的类型

function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name:string;
run():void;
}
const tom = getCacheData("tom") as Cat;

​getCacheData​​​执行以后返回的是​​any​​​类型,在赋值变量​​tom​​​时候可以将其断言为​​Cat​​​类型。后续对​​tom​​的访问时就有了代码补全,提高了代码的可维护性。

三、类型断言的限制

综上所述,类型断言有以下特点:

  1. 联合类型可以被断言为其中一个类型
  2. 父类可以被断言为子类
  3. 任何类型都可以被断言为​​any​
  4. ​any​​可以被断言为任何类型

但类型之间的断言却是有限制的。若A能兼容B,那么可以将A断言为B,也可以将B断言为A。

interface Animal {
name:string;
}
interface Cat {
name:string;
run():void;
}
const tom:Cat = {
name:"tom",
run(){
console.log("running");
}
}
const animal:Animal = tom;

TS是结构类型系统,类型之间的对比只会比较它们最终的结构,而会忽略它们定义时的关系。所以在上例中,尽管​​Animal​​​、​​Cat​​​定义时并没有任何关系,但结构上可以理解为​​Cat​​​继承于​​Animal​​:

interface Cat extends Animal{
run():void;
}

即:​​Animal​​​兼容​​Cat​​。此时二者可以互相断言:

function testAnimal(animal:Animal){
return animal as Cat
}
function testCat(cat:Cat){
return cat as Animal;
}
  • 允许 ​​animal as cat​​ 是因为父类可以被断言为子类。
  • 允许 ​​cat as animal​​ 是因为子类拥有父类的所有属性和方法,调用也不会出问题。

综上所述:

  1. 联合类型可以被断言为其中一个类型
  2. 父类可以被断言为子类
  3. 任何类型都可以被断言为​​any​
  4. ​any​​可以被断言为任何类型
  5. 要使得A能够被断言为B,需要A兼容B或B兼容A

前四种情况都是最后一种的特例。

四、双重断言

既然:

  1. 任何类型都可以被断言为​​any​
  2. ​any​​可以被断言为任何类型

那么类型岂不可以互相断言了?

const a:number = 666;
const b:string = a as any as string;

但这么做反而会导致一系列问题,所以,不到万不得已,最好不要这么做。

五、类型断言 VS 类型转换

类型断言只会影响TS编译时的类型,类型断言语句会在编译结果中删除。

function toBoolean(something: any): boolean {
return something as boolean;
}
toBoolean(1);
// 返回值为 1
而类型转换则会影响编译结果:

function toBoolean(something: any): boolean {
return Boolean(something);
}
toBoolean(1);
// 返回值为 true

六、类型断言 VS 类型声明

类型断言:

function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;

类型申明:

const tom:Cat = getCacheData('tom');

因为​​getCacheData()​​​的返回类型是​​any​​,所以这个例子二者效果是一样的。但二者还是有区别的:

interface Animal{
name:string;
}
interface Cat {
name:string;
run():void;
}
const animal:Animal = {
name:"tom"
}
const tom = animal as Cat;

但类型声明却不行:

const tom:Cat = animal;
//Property 'run' is missing in type 'Animal' but required in type 'Cat'

​Animal​​​兼容​​Cat​​​,所以可以将​​animal​​​断言为​​Cat​​​,赋值给​​tom​​​。但直接申明​​tom​​​为​​Cat​​类型是不可以的。

深入的讲,它们的核心区别就在于:

  • ​animal​​​ 断言为 ​​Cat​​​,只需要满足 ​​Animal​​​ 兼容 ​​Cat​​​ 或 ​​Cat​​​ 兼容 ​​Animal​​ 即可
  • ​animal​​​ 赋值给 ​​tom​​​,需要满足 ​​Cat​​​ 兼容 ​​Animal​​ 才行

但是 ​​Cat​​​ 并不兼容 ​​Animal​​。