any

基本含义

any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值

any|unknown|never 类型 - 图1

变量类型一旦设为any,TypeScript 实际上会关闭这个变量的类型检查。即使有明显的类型错误,只要句法正确,都不会报错。应该尽量避免使用any类型,否则就失去了使用 TypeScript 的意义。实际开发中,any 类型主要适用以下两个场合

  1. 出于特殊原因,需要关闭某些变量的类型检查,就可以把该变量的类型设为 any
  2. 为了适配以前老的 JavaScript 项目,让代码快速迁移到 TypeScript,可以把变量类型设为any,TypeScript 编译时就不会报错

总之,TypeScript 认为,只要开发者使用了 any 类型,就表示开发者想要自己来处理这些代码,所以就不对any类型进行任何限制,怎么使用都可以。从集合论的角度看,any 类型可以看成是所有其他类型的全集,包含了一切可能的类型。TypeScript 将这种类型称为“顶层类型”(top type),意为涵盖了所有下层

类型推断问题

对于开发者没有指定类型,TypeScript 必须自己推断类型的那些变量,如果无法推断出类型,TypeScript 就会认为该变量的类型是 any

any|unknown|never 类型 - 图2

这显然是很糟糕的情况,所以对于那些类型不明显的变量,一定要显式声明类型,防止被推断为 any。TypeScript 提供了一个编译选项 noImplicitAny,打开该选项,只要推断出 any 类型就会报错

any|unknown|never 类型 - 图3

上面命令使用了 noImplicitAny 编译选项进行编译,这时上面的函数 add() 就会报错。这里有一个特殊情况,即使打开了 noImplicitAny,使用 let 和 var 命令声明变量,但不赋值也不指定类型,是不会报错的

any|unknown|never 类型 - 图4

const 命令没有这个问题,因为 JavaScript 语言规定 const 声明变量时,必须同时进行初始化(赋值)。const 命令声明的 x 是不能改变值的,声明时必须同时赋值,否则报错,所以它不存在类型推断为 any 的问题

污染问题

any 类型除了关闭类型检查,还有一个很大的问题,就是它会“污染”其他变量。它可以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错。污染其他具有正确类型的变量,把错误留到运行时,这就是不宜使用 any 类型的另一个主要原因

any|unknown|never 类型 - 图5

unknown

为了解决 any 类型“污染”其他变量的问题,TypeScript 3.0 引入了 unknown 类型。它与 any 含义相同,表示类型不确定,可能是任意类型,但是它的使用有一些限制,不像 any 那样自由,可以视为严格版的 any。unknown 跟 any 的相似之处,在于所有类型的值都可以分配给 unknown 类型

any|unknown|never 类型 - 图6

unknown 类型跟 any 类型的不同之处在于,它不能直接使用。主要有以下几个限制

  • 首先,unknown类型的变量,不能直接赋值给其他类型的变量(除了any类型和unknown类型)

any|unknown|never 类型 - 图7

  • 其次,不能直接调用 unknown 类型变量的方法和属性

any|unknown|never 类型 - 图8

  • 再次,unknown 类型变量能够进行的运算是有限的,只能进行比较运算(运算符==、===、!=、!==、||、&&、?)、取反运算(运算符!)、typeof 运算符instanceof 运算符 这几种,其他运算都会报错

any|unknown|never 类型 - 图9

那么,怎么才能使用 unknown 类型变量呢?答案是只有经过“类型缩小”,unknown 类型变量才可以使用。所谓“类型缩小”,就是缩小 unknown 变量的类型范围,确保不会出错

any|unknown|never 类型 - 图10

上面示例中,unknown 类型的变量 a 经过 typeof 运算以后,能够确定实际类型是 number,就能用于加法运算了。这就是“类型缩小”,即将一个不确定的类型缩小为更明确的类型。这样设计的目的是,只有明确 unknown 变量的实际类型,才允许使用它,防止像 any 那样可以随意乱用,“污染”其他变量。在集合论上,unknown 也可以视为所有其他类型(除了any)的全集,所以它和 any 一样,也属于 TypeScript 的顶层类型

never

为了保持与集合论的对应关系,以及类型运算的完整性,TypeScript 还引入了“空类型”的概念,即该类型为空,不包含任何值。由于不存在任何属于“空类型”的值,所以该类型被称为 never,即不可能有这样的值

any|unknown|never 类型 - 图11

  1. never 类型的使用场景,主要是在一些类型运算之中,保证类型运算的完整性。另外,不可能返回值的函数,返回值的类型就可以写成 never
  2. 如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于 never 类型

any|unknown|never 类型 - 图12

  1. never 类型的一个重要特点是,可以赋值给任意其他类型

any|unknown|never 类型 - 图13

为什么 never 类型可以赋值给任意其他类型呢?这也跟集合论有关,空集是任何集合的子集。TypeScript 就相应规定,任何类型都包含了 never 类型。因此,never 类型是任何其他类型所共有的,TypeScript 把这种情况称为“底层类型”(bottom type)。总之,TypeScript 有两个“顶层类型”(any 和 unknown),但是“底层类型”只有 never 唯 一 一 个