TypeScript最佳实践(二)类型断言、类型守卫

736 阅读7分钟

1. TS 类型断言定义

把两种能有重叠关系的数据类型进行相互转换的一种 TS 语法,把其中的一种数据类型转换成另外一种数据类型。类型断言和类型转换产生的效果一样,但语法格式不同。

2. TS 类型断言语法格式

A 数据类型的变量 as B 数据类型,A 数据类型和 B 数据类型必须具有重叠关系

1.“尖括号” 语法

let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;

2.as 语法

let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

3. 理解重叠关系

1. A,B都为类

  1. 如果 A,B 是类并且有继承关系

【 extends 关系】无论 A,B 谁是父类或子类, A 的对象变量可以断言成 B 类型,B 的对象变量可以断言成A类型。但注意一般在绝大多数场景下都是把父类的对象变量断言成子类。

  1. 如果 A,B 如果是类,但没有继承关系

两个类中的任意一个类的所有的 public 实例属性【不包括静态属性】加上所有的 public 实例方法和另一个类的所有 public 实例属性加上所有的 public 实例方法完全相同或是另外一个类的子集,则这两个类可以相互断言,否则这两个类就不能相互断言。

2. A 是类,B 是接口

  1. A 类实现了 B 接口【implements】,则 A 的对象变量可以断言成 B 接口类型,同样 B 接口类型的对象变量也可以断言成A类型 。

  2. A 类没有实现 B 接口,则断言关系和第2项的规则完全相同。

3. A 是类,B 是 type 定义的数据类型

【就是引用数据类型,例如 Array, 对象,不能是基本数据类型,例如 string,number,boolean】,

  1. A 类实现了 B type 定义的数据类型【 implements】,则 A 的对象变量可以断言成 B type 定义的对象数据类型,同样 B type 定义的对象数据类型的对象变量也可以断言成 A 类型 。

  2. A 类没有实现 B type定义的数据类型,则断言关系和第2项的规则完全相同。

4. A 是一个函数上参数变量的联合类型

如果 A 是一个函数上参数变量的联合类型,例如 string |number,那么在函数内部可以断言成 string 或number 类型。

5. 多个类组成的联合类型

例如:let vechile: Car | Bus | Trunck。 vechile 可以断言成其中任意一种数据类型。 例如 vechile as Car, vechile as Bus , vechile as Trunck;

6. any 或 unknown

任何数据类型都可以转换成 any 或 unknown 类型,any 或 unknown 类型也可以转换成任何其他数据类型。

4. ⾮空断⾔

在上下⽂中当类型检查器⽆法断定类型时,⼀个新的后缀表达式操作符 ! 可以⽤于断⾔操作对象是⾮ null 和⾮ undefined 类型。具体⽽⾔,x! 将从 x 值域中排除 null 和 undefined。

const a: number | undefined = undefined;

const b: number = a!;

console.log(b);

5. 确定赋值断⾔

在 TypeScript 2.7 版本中引⼊了确定赋值断⾔,即允许在实例属性和变量声明后⾯放置⼀个! 号,从⽽告诉 TypeScript 该属性会被明确地赋值

let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
    x = 10;
}

6. 类型守卫

在讲类型守卫前,我们先来了解下 new 底层发生了什么

function Person(phone,age){
    this.age=age; age实例属性
    this.phone=phone; phone实例属性
    this.showone=function(){} showone实例方法
}
Person.prototype.doEat=function(){ 
    console.log("电话:"this.phone)
}

let person=new Person("12344",23)
// new 一个实例对象的底层3步
1. 创建一个 Object 对象
var obj = new Object(); 
var obj={}

// 2.让新创建的对象的 __proto__ 变量指向 Person 原型对象空间
obj.__proto__=Person.prototype;

// 3.借用Person构造函数中的为 obj 对象变量增加 age 属性和 phone 属性
Person.apply(obj,["12344",23]);

类型保护是可执⾏运⾏时检查的⼀种表达式,⽤于确保该类型在⼀定的范围内。 换句话说,类型保护可 以保证⼀个字符串是⼀个字符串,尽管它的值也可以是⼀个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、⽅法或原型,以确定如何处理值

类型保护的四种方式

  • 类型判断:typeof
  • 实例判断:instanceof
  • 属性或者方法判断:in
  • 字面量相等判断:=====!=!==

1. typeof

作用

typeof 用来检测一个变量或一个对象的数据类型。

typeof 检测的范围

typeof 检测变量的类型范围包括: “string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" 等数据类型。

typeof 的局限性

typeof 检测变量并不完全准确,例如 typeof null 结果为 object,这其实设计者的一个 bug, 但后来一直没有被改过来,于是就此传下来了,但把 null 当成 object 的理由说成是未来可能会拥有一个对象空间,这个理由很牵强【我们检测的是对象变量此刻的类型】,null 本来即是数据类型,也是值。所以 typeof null 直接显示 null 最合适了。

再例如:使用 typeof 来检测一个数组变量,typeof [ ] 结果显示的是 object, 从 Array 创建的本质上来说确实是 object,但我们期待看到的是 Array,这更符合预期。 Array 和我们定义的普通函数一样,具有双重性,当成函数类型时用来创建数组对象,但也是一个构造函数对象,拥有静态成员和prototype原型对象属性。

再比如:使用 typeof 来检测一个 Set 变量,Map 变量,结果显示的是依然是 object。

typeof 的替代方案

// Object.prototype.toString.call

Object.prototype.toString.call ([ ]) // 展示[object Array]

Object.prototype.toString.call(null) // 展示[object null]

Object.prototype.toString.call(Set类型的变量) // 展示[object Set]

Object.prototype.toString.call(Map类型的变量) // 展示[object Map]

typeof 的替代方案依然无法解决的问题

就是无法获取一个自定义的类的实例变量或构造函数的对象变量的真正创建类型,答案是使用 instanceof 来解决。

2. instanceof

instanceof 格式

对象变量 instanceof 类名或函数名

instanceof 的主要作用

instanceof 帮助我们准确的判断一种自定义函数或类创建的对象变量的数据类型。

instanceof 执行后 返回 true 的几种条件【符合一个即可】

1)对象变量.__proto=类名或函数名.prototype。

解释1:如果 instanceof 关键字左边对象变量的 __proto 属性指向的原型对象空间=右边类名或函数名的 prototype 对象属性指向的原型对象空间,那么返回 true。

2)对象变量.__proto.__proto...__proto=类名或函数名.prototype。

解释2:instanceof 左边对象变量 __proto__的1到多个上一级__proto__指向的原型对象空间,等于右边类名或函数名的 prototype 对象属性指向的原型对象空间,那么也返回 true,按照这个查找规律,会一直找到Object.prototype 对象属性指向的原型对象空间为止。

interface Padder {
    getPaddingString(): string;
}

class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) {}
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }
}


let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
    // padder的类型收窄为 'SpaceRepeatingPadder'
}

3. in

如果指定的属性在指定的对象或其原型链中,则in 运算符返回true

const car = { make: 'Honda', model: 'Accord', year: 1998 };
console.log('make' in car);

7. 自定义守卫

function  函数名(形参:参数类型【参数类型大多为any】) : 形参 is A类型 =boolean+类型守卫能力{
    return  true or false
}

理解:返回布尔值的条件表达式赋予类型守卫的能力, 只有当函数返回 true 时,形参被确定为 A 类型

例如:

function isNumber(x: any): x is number {

    return typeof x === "number";

}

function isString(x: any): x is string {

    return typeof x === "string";

}

8. const 为何也能被修改

as const 与 readonly 是匹配出现的,如下

//const arr=[10,30,40,"abc"]
//arr=[100,30,40,"abc"]
//arr[0]=100

const arr = [10, 30, 40, "abc"] as const
//arr = [100, 30, 40, "abc"]
//arr[0] = 100;//错误 无法分配到 "数组的索引为0位置的元素" ,因为它是只读属性

function showArr(arr: readonly any[]) {//类型“readonly any[]”中的索引签名仅允许读取。
  //arr[0] = 100;
  console.log(arr)
}

showArr(arr)