TypeScript 的变量与变量类型¶
TypeScript 最大的特点就是引入了类型系统,这样就可以在编译为 JavaScript 代码之前由编译器进行类型检查。在这样的条件下,TypeScript 中的变量在声明的时候就可以指定类型,编译器在将 TypeScript 代码编译为 JavaScript 代码的时候会进行类型检查,若有不符合类型声明的情况则会报错:
1 2 3 4 5 |
|
变量类型¶
由于 TypeScript 是 JavaScript 的超集,所以其基础变量类型依然和 JavaScript 一致。
声明变量时可以在变量后面标注,也可以根据初始值自动推断,但如果声明变量时不赋初始值,则必须添加类型标注,否则在使用时会报错(即自动推断该变量为 undefined
的类型,因此不能赋其他值)。
常用的基础类型包括:
1 2 3 |
|
原始值¶
-
布尔类型(
boolean
):1
let sast_yyds: boolean = true;
-
null
类型:1
let null_value: null = null;
-
undefined
类型:1
let undefined_value: undefined = undefined;
-
数字类型(
number
):1
let sast_rank: number = 1;
-
bigint
类型(ES 2020 新增):1
let a: bigint = 9007199254740991n;
-
字符串类型(
string
):1
let course: string = 'TypeScript introduction';
-
符号类型(
symbol
,ES 6 新增):1
let sym: symbol = Symbol('SAST');
-
尽管 TypeScript 会尝试阻止你这么做,但 JavaScript 是动态类型语言,你可以将一个变量赋为其他类型值。
-
尽管 TypeScript 更加会尝试阻止你,但 JavaScript 是弱类型语言,要小心随处可能存在的隐式类型转换,这可能会引起很难理解的行为,关于这一现象的详细解释可以参考 JavaScript 的变量与变量类型中的“JavaScript 的魔法”一节。
1 2
console.log(1 + null); // => 1 console.log(1 + undefined); // => NaN
类型标注¶
简单类型标注¶
上文已经提到原始值的类型标注方式,但需要注意的是 TypeScript 允许使用字面量作为类型标注,如:
1 2 |
|
这里变量 one
的类型被限定为字面量 1
而不是所有的 number
,这种标注的作用在下面会展示。
当你不知道该标记什么类型,或者你希望可以写任何类型时,可以谨慎使用 any
,编译器将不会尝试对 any
类型的变量做任何的分析。
1 2 3 4 5 |
|
注意可能的 any
类型滥用
any
类型是目前 TypeScript 语言之中具有较大争议的一个设计,因为理论上我们可以将所有的变量声明为 any
从而绕过类型检查,这个时候 TypeScript 实际上退化为 JavaScript。
但是考虑到目前 Web 前端项目会引用大量的第三方库,开发者很多时候无法完全把握某些变量的信息,所以 any
类型是必要的。不过我们需要注意其使用,对于能够给定类型的变量则尽量不标记为 any
。
针对这一点,部分代码检查工具已经实装了检查“可疑 any
”的功能。
类型别名¶
可以使用 type
关键词定义类型别名,在需要实现复杂的(尤其是嵌套的)类型时非常有用:
1 2 3 |
|
对象和数组的类型¶
对象和数组的类型应当这样标注:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
1 2 3 4 |
|
对于固定长度和类型的数组(这实际上更接近于其他语言中的元组),可以这样标注:
1 |
|
联合类型和类型收窄¶
TypeScript 支持将变量声明为若干个类型之中的一种,这一般称为联合类型(Union Type)。
声明联合类型的时候,多个类型之间使用 |
隔开:
1 2 |
|
联合类型最常用的地方是标注函数参数,这样就允许了函数接受多种类型的参数:
1 2 3 4 5 6 |
|
对于上面提到的错误,可以这样解决:
1 2 3 4 5 6 7 8 9 |
|
这里我们声明 frontend
可能有两种类型,即 string
和 string[]
,所以我们可以将 frontend
任意赋值为其中的一种,但这就导致我们在使用这一值时,不能精确判断上面包含的方法,例如我们尝试执行:
1 |
|
编译器会告诉我们类型“string[]”上不存在属性“match”,因此编译器无法确定这行代码是正确的,但如果我们的代码足以使编译器推断出具体类型,如:
1 2 3 4 5 |
|
编译器就会明白在两个分支上分别是两种类型,并允许调用相应的方法,这种行为被称为类型收窄。
联合类型实现枚举类型¶
上文提到字面量可以作为类型标注,那么使用联合类型,就可以实现枚举行为:
1 2 3 |
|
类型断言¶
当你确信你比编译器拥有关于某一变量类型的更多知识时,可以使用类型断言告诉编译器服从你的断言:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
其中 as
关键词表示类型断言,但你不能断言一个变量为明显冲突的类型,如:
1 |
|
除非你有绝对的信心时,可以先将类型断言为 unknown
再断言为任意其他类型:
1 |
|
never
类型与耗尽检查¶
TypeScript 支持一种特殊的类型,即 never
类型。这种类型常被用于标注函数返回值,代表这个函数永远不会终结或者会抛出异常:
1 2 3 |
|
这种类型的值永远不能被实例化,也即尝试声明和使用 never
类型的值将会总是出现错误,利用个特点,我们可以检测程序是否考虑了所有的情况,这被称为耗尽检查(Exhaustive check)。
例如我们需要编写函数处理联合类型 number | string | boolean
,其代码可能为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
根据 never
的特点,default
分支的代码执行必然会产生错误,因此如果该 switch
语句未能穷尽 typeof value
的可能取值(而使得代码落入 default
分支),就会导致编译器报错。
如果修改类型 All
为 number | string | boolean | undefined
,编译器会告诉我们不能将类型“undefined”分配给类型“never”,这就是因为当 value === undefined
时,会尝试将 undefined
赋给 never
类型的变量。
这样,handler
函数就会因为没有耗尽所有可能而报错,提醒开发者需要更新这个函数。
这种处理方式类似于 C++ 程序在不可能的分支上加入 assert(false)
进行检测。