发布时间:2022-11-20 文章分类:WEB开发 投稿人:樱花 字号: 默认 | | 超大 打印

简介

我们知道在编码时即使不标注变量类型,TypeScript 编译器也能推断出变量类型,那 TypeScript 编译器是怎么进行类型推断,在类型推断时又是如何判断兼容性的呢?

此文,正好为你解开这个疑惑的,掌握本文讲解的类型推断与类型放宽知识点后将对 TypeScript 的类型系统有更深的认识。

不妨先看看下面几个问题,如果你都能回答上,那么可以不用阅读此文了。

let x = 0;
const y = 0;
function f() {
return 0
}
let x = f();
const list = ['hello', 0];
const x = 0;
let y = x;
const a: 0 = 0;
let b = a;

类型推断与放宽概念

我们知道 JS 中表达式都具有返回值,在 TypeScript 程序中表达式也一样具有返回值的同时还具有一种类型(返回值的类型),且此类型来源分为:类型注解类型推断

类型注解是通过编写代码手动指定表达式返回值的类型,如下代码:

let x: number = 0; // 通过类型注解指定变量 x 为 number 类型

类型推断指的是 TypeScript 编译器自动推测表达式返回值的类型,是一种比较智能的类型推测方法,可以简化代码,如下代码:

let x = 0; // 这里 TypeScript 编译器自动推断变量 x 为 number 类型

上面两段代码中字面量 Literal 的值明明是字面量类型 0,但是变量 x 却变为了 number 类型。值的类型和推断的变量类型不一致,这就涉及到 TypeScript 的类型放宽了。

常规类型推断

上述代码定义了变量 x 并给其赋值了初始值,属于常规类型推断。

下面代码中,变量 x 具有初始值 0,编译器推断其类型为 number 类型。

TS中的类型推断与放宽实例详解

下面代码中,变量 x 具有初始值 0,但是使用了 const 关键字定义其为常量,故编译器推断其类型为字面量类型 0

TS中的类型推断与放宽实例详解

假如变量声明时未指定初始值呢?这时,编译器将其自动推断为 any 类型。根据[[子类型兼容性]]章节中介绍可知,any 类型属于顶端类型之一,不是任意类型的子类型,但是却与任意类型满足赋值兼容性,这样未指定初始值的变量 x 后面可以被被赋值为任意类型。

TS中的类型推断与放宽实例详解

最佳通用类型

编译器在进行类型推断过程中,有可能推断出多个可能得类型,并会参考所有可能的类型得出最终的最佳通用类型。

这里得出的类型可能为字面量 hello 对应的原始类型 string、字面量 0 对应的原始类型 number,得出的最佳通用类型为 string | number

const list = ['hello', 0]; // (string | number)[]

这里正好解释了开篇提出的问题 3

当数组的成员类型存在子类型关系时,最佳通用类型也会有所不同。

这里 list1 根据可能的类型 A、B 得出最佳通用类型为 A | Blist2 所有可能的类型有 A、B、Base,但是存在[[子类型兼容性]]: A <- BaseB <- Base,所以得出的最佳通用类型为 Base

class Base {
version: string = '1.0.0'
}
class A extends Base {}
class B extends Base {}
const list1 = [new A(), new B()] // (A | B)[]
const list2 = [new A(), new B(), new Base()] // Base[]

代码运行验证如下:

TS中的类型推断与放宽实例详解

按上下文归类

上文说的常规类型推断、最佳通用类型都是由表达式的结果推导对应变量的类型,这是一个由右向左的推断过程。TypeScript 编译器还能够由变量的类型来推导变量对应初始值的类型,这是一个由左向右的推断过程

TS中的类型推断与放宽实例详解

这里指定变量 f 为 AddFunction 类型,给定的初始值是一个函数,并且这个函数的形参和返回值都未指定类型,编译器会自动根据 f 的类型推导出初始值的形参和返回值类型。

interface AddFunction {
(x: number, y: number): number;
}
let f: AddFunction = (x, y) => {
return x + y;
}

编译器按上下文归类推断出的类型如下:

TS中的类型推断与放宽实例详解

类型放宽

上文在介绍最佳通用类型时提到过“字面量 hello 对应的原始类型 string”,这就属于类型放宽。编译器在进行类型推断时候会进行类型放宽,比如字面量类型 hello 放宽为原始类型 string。同样,下面变量 x 也会被放宽为 number 类型。

let x = 0; // number

类型放宽分为:常规类型放宽、字面量类型放宽两类,见下文。

常规类型放宽

undefinednull 类型会被编译器放宽为 any 类型,不过这一特性在配置的编译器检查规则 --strictNullChecks 不同时情况不一样。

非严格类型检查模式

修改 tsconfig.json 配置文件为如下:

{
"compilerOptions": {
"strictNullChecks": false
}
}
let x1 = undefined; // any
const x2 = undefined; // any
let y1 = null; // any
const y2 = null; // any

此模式下,undefined 的值依然是 undefined 类型(null 同理),只是编译器在进行类型推断时将 undefined 类型放宽为了 any 类型。

TS中的类型推断与放宽实例详解

严格类型检查模式

修改 tsconfig.json 配置文件为如下:

{
"compilerOptions": {
"strictNullChecks": true
}
}
let x1 = undefined; // undefined
const x2 = undefined; // undefined
let y1 = null; // null
const y2 = null; // null

此模式下,编译器不会对 undefined、null 类型进行放宽,undefined 的值依然是 undefined 类型(null 同理)。

TS中的类型推断与放宽实例详解

字面量类型放宽

字面量类型在进行类型推断时,若当前表达式的值是可变的,则会对字面量的类型进行放宽,放宽规则如下表。

TS中的类型推断与放宽实例详解

开篇的问题 1 中的代码见下方,定义了两个表达式,之前 let 定义的表达式值是可变的,const 定义的表达式值是不可变的。因此,变量 x 类型按照字面量进行放宽为 string 类型,变量 y 类型不会进行放宽,为字面量类型 0。

let x = 0;
const y = 0;

对象、数组字面量类型的放宽

上文以表达式的值是否可变的角度来看待字面量类型是否可以放宽并非十分恰当,对于使用 const 关键字定义的对象、数组的情况则稍有不同。

JS 中 const 定义的变量不可变指的是变量指向的指针不可变,但是对象、数组是引用类型,当对象的属性或数组的元素的值变化(或者指向的指针变化)时,该变量的指针并未改变。

因此,对象、数组字面量类型在进行推断时也会进行类型放宽,这正是开篇的问题 3 的解答。

下面代码 base.version 的类型会进行放宽,结果类型为:number,base.author 同样,放宽为:string。

const base = {
version: 1,
author: 'JohnieXu'
};

下面代码 list 的类型会进行放宽,结果类型为:(string | number)[]

const list = ['hello', 0];

类字面量类型的放宽

类字面量和对象字面量比较相似,因为在类在 JS 中(或者说 JS 解释器)也是通过对象进行模拟的,不同仅在于类的属性具有修饰符。对于具有 readonly 修饰符的对象属性,因其值不可变,故不会进行类型放宽。

TS中的类型推断与放宽实例详解

函数返回值字面量类型的放宽

在函数或方法中,若返回值的类型为字面量类型,则编译器推断的返回值类型会放宽;若返回值的类型为字面量联合类型,则不会放宽。

TS中的类型推断与放宽实例详解

TS 内部类型放宽规则

每个字面量类型都有一个内置属性表示其是否可以被放宽,而 TypeScript 编译器会根据放宽规则来推断出这个内置属性。

在 TypeScript 语言内部实现中,根据字面量的来源不同进行了分类,来自于表达式的字面量类型标记为全新的(fresh)字面量类型。只有全新的字面量类型才是可放宽的字面量类型,并且根据字面量处于表达式的位置,分为:可变值位置、不可变值位置。

因此,字面量的类型可放宽的充分必要条件为:为全新的字面量类型,且在代码中处于可变值的位置

TS中的类型推断与放宽实例详解

实例分析

以开篇的问题 4 中部分代码为例:

const x = 0;
let y = x;

变量 x、y 的类型见下图,可见两者类型并不相同,x 类型未放宽,y 类型有放宽。

TS中的类型推断与放宽实例详解

分析过程如下:

下面还是以开篇的问题 4 中部分代码为例(说明使用了类型注解的场景):

const a: 0 = 0;
let b = a;

变量 a、b 的类型见下图,可见两者类型相同,都没有类型放宽。

TS中的类型推断与放宽实例详解

分析过程如下:

开篇问题解答

开篇提出的问题中 1、3、4 已在上文讲解过程中进行过分析,这里分析一下问题 2 。

function f() {
return 0
}
let x = f();

先看这个问题的答案,如下:

TS中的类型推断与放宽实例详解

分析过程:

以上就是TS 中的类型推断与放宽实例详解的详细内容,更多关于TS类型推断与放宽的资料请关注本站其它相关文章!