我们知道,JavaScript是弱类型语言,可以为变量赋予任意类型的值,且可以随时对其进行改变(const除外)。

这会造成很多的“运行时”的未知问题,而TypeScript的出现,把变量类型检查加入进来,且提前到了“编译期”,可以帮助我们提前暴露一些潜在的问题。

但除了常规的“string、number”等类型之外,为了考虑更多特殊情况,TypeScript也具备一些特殊类型,来简单介绍一下,帮助大家区分。

any

允许被赋予不同类型的值

TypeScript中的变量是需要在定义时声明类型的,或者,会根据定义时所赋值的类型来“推测”类型。在之后的编码中,类型就不允许更改了,否则会报错。

但有时候,这种情况是需要被允许的,比如:和未知第三方进行交互。该怎么办呢,any可以派上用场啦。

any是TypeScript中比较特殊的一个类型,它被称为任意类型,通过它声明的变量可以被赋予任何类型的值。

1
2
3
4
let a:any = 0
a = 'hello'
a = null
a = undefined

但使用时也要格外小心,因为TypeScript不会再检查类型了,就有可能出现调用某类型特有方法的时候报错的情况,比如期待类型是number,有toFixed()方法,而得到的值却是string 类型,就会报错,这样就失去了使用TypeScript的好处了。

允许赋值给不同类型

除了可以将任意类型的值赋给any类型的变量,还可以将any类型的值赋给其他任何类型的变量。

1
2
3
let a:number
let b:string = 'hello'
a = b

总结一句话就是:它能逃过TypeScript编译器的类型检测,并和TypeScript中其他所有类型都相互兼容

既然any类型如此随意,那作为强类型编程语言的TypeScript为什么要引入它呢?答案很简单,因为有时候我们可能需要在TypeScript代码中跟一些现有的JavaScript代码(库)交互,而JavaScript代码返回的数据的类型是未知的,但TypeScript又要求数据必须有一个类型,此时any便是这样一个类型。

unknown

可以被赋予任何类型的值

unknown 和 any 极为相似,它可以被赋予任何类型的值,我们必须要合理地使用未知类型,因为它同样容易带来意外的运行时错误。

为了尽可能地避免这样的错误,TypeScript对未知类型做了一个限制,那就是不允许我们将未知类型的值赋给其他类型(any除外)的变量

这就是它跟 any 的不同之处了,它和其他类型并不是相互兼容的,它只可以被赋值,不能将自己赋值给其他类型。

void

没有任何类型

void 和 any 相反,它表示没有任何类型。

尽管如此,void类型的变量还是可以被赋予一个特殊的值,即undefined。但声明可被赋值为undefined的变量不是void类型的作用所在,其真正的作用是指定函数的返回类型

1
2
3
function hello():void{
console.log('hello')
}

这样的定义就表明,函数 hello 不返回任何值。并且,函数的返回类型一旦被指定为void,它便不能返回任何类型的值(undefined除外)。

不过,有一种特殊情况是,返回空。

1
2
3
function hello():void{
return;
}

事实上,这两种情况都归为——“函数返回了undefined”。

never

never表示不存在的值的类型。我们可以声明一个never类型的变量,但无法给这个变量赋任何类型的值。

既然如此,never的用途是什么呢?

它可以用于指定一个永远无法返回的函数的返回类型。

什么函数不会返回值呢?比如:错误处理函数,无限循环函数。

1
2
3
4
5
6
7
8
9
function error():never{
throw '错误'
}

function finish():never{
while(true){
console.log('事情已经完成了')
}
}

大多数函数总能顺利地执行到终点,但对于以上两个函数来说,它们要么因为抛出了异常而终止了执行,要么因为无限循环而不可能结束。因此,它们都不存在返回值,或说都返回了一个不存在的值。

那么,指定一个函数的返回类型为never有什么实际意义呢?大多数情况下,我们不会期望一个函数抛出异常或陷入无限循环中,但在希望获得编译器完整性检查时,就可以这么做。

类型断言

特殊类型讲完了,还可以顺便聊聊“类型断言”,什么是“类型断言”。

在很多其他编程语言中都有类型转换(Type Cast)的概念,但TypeScript中并不存在,因为类型转换发生在运行时,而TypeScript只能获得编译时支持。

作为补偿,TypeScript提供了另一个被称为类型断言(TypeAssertion)的概念。通过类型断言,我们可以告诉TypeScript编译器,我们明确地知道某个变量具备更加精确的类型。

以下是一个未使用类型断言的例子:

1
2
let res:any = '未知字符串' 
let resLength:number = res.length

正常情况下,我们理解的字符串是具备length属性的,编辑器也会给出提示,但是,这里res被定义了any类型,代码编译会正常通过,编辑器却无法给出提示,这时候,类型断言就派上用场。

加入断言后,上面代码可以改成下面这样:

1
2
3
4
5
let strRes:string = <string>res
res.length:number = strRes.length

//或者还有一种更简洁的 as 写法

let resLength:number = (res as string).length

使用了断言之后,浏览器就能“感知”到原先被使用未知类型的变量具体是什么类型,就可以改善开发体验。

在使用类型断言时,我们需要注意两点:

一、不能进行类型不兼容的类型断言;
二、类型断言无法修改变量的运行时类型。

1
2
3
let res:any = '未知字符串' 
let resLength:number = res as number
resLength = resLength * 2

字符串不能是 number,第二行无效;字符串没有乘法运算,第三行无效。

总结

至此,TypeScript中的特殊类型就聊完了,为什么单聊这些呢,因为我们往往会重点学习那些更具体,更实用的类型,而不自觉地忽略掉这些特殊类型,把它们弄懂了,知道怎么用,还是有利于更好地使用 TypeScript的,一起加油吧!