TypeScript核心语法(3)——类型系统
本章是 TypeScript 类型系统的总体介绍。
TypeScript 继承了 JavaScript 的类型,在这个基础上,定义了一套自己的类型系统。
先讲一下最基础的类型,any类型。
any 类型
基本含义
any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。
let x: any;
x = 1; // 正确
x = "foo"; // 正确
x = true; // 正确
上面示例中,变量x
的类型是any
,就可以被赋值为任意类型的值。
变量类型一旦设为any
,TypeScript 实际上会关闭这个变量的类型检查。即使有明显的类型错误,只要句法正确,都不会报错。
let x: any = "hello";
x(1); // 不报错
x.foo = 100; // 不报错
上面示例中,变量x
的值是一个字符串,但是把它当作函数调用,或者当作对象读取任意属性,TypeScript 编译时都不报错。原因就是x
的类型是any
,TypeScript 不对其进行类型检查。
由于这个原因,应该尽量避免使用any
类型,否则就失去了使用 TypeScript 的意义。
总之,TypeScript 认为,只要开发者使用了any
类型,就表示开发者想要自己来处理这些代码,所以就不对any
类型进行任何限制,怎么使用都可以。
从集合论的角度看,any
类型可以看成是所有其他类型的全集,包含了一切可能的类型。TypeScript 将这种类型称为“顶层类型”(top type),意为涵盖了所有下层。
类型推断问题
对于开发者没有指定类型、TypeScript 必须自己推断类型的那些变量,如果无法推断出类型,TypeScript 就会认为该变量的类型是any
。
typescript
function add(x, y) {
return x + y;
}
add(1, [1, 2, 3]); // 不报错
上面示例中,函数add()
的参数变量x
和y
,都没有足够的信息,TypeScript 无法推断出它们的类型,就会认为这两个变量和函数返回值的类型都是any
。以至于后面就不再对函数add()
进行类型检查了,怎么用都可以。
这显然是很糟糕的情况,所以对于那些类型不明显的变量,一定要显式声明类型,防止被推断为any
。
let x;
x = 123;
x = { foo: "hello" };
上面示例中,变量x
的类型推断为any
,但是不报错,可以顺利通过编译。
由于这个原因,建议使用let
和var
声明变量时,如果不赋值,就一定要显式声明类型,否则可能存在安全隐患。
const
命令没有这个问题,因为 JavaScript 语言规定const
声明变量时,必须同时进行初始化(赋值)。
const x; // 报错
上面示例中,const
命令声明的x
是不能改变值的,声明时必须同时赋值,否则报错,所以它不存在类型推断为any
的问题。
基本类型
概述
JavaScript 语言(注意,不是 TypeScript)将值分成 8 种类型。
- boolean
- string
- number
- bigint
- symbol
- object
- undefined
- null
TypeScript 继承了 JavaScript 的类型设计,以上 8 种类型可以看作 TypeScript 的基本类型。
这 8 种基本类型是 TypeScript 类型系统的基础,复杂类型由它们组合而成。但是其实我感觉用的最多的就是三种,我就只介绍三种类型,其他五种就不介绍了。
boolean 类型
boolean
类型只包含true
和false
两个布尔值。
const x: boolean
上面示例中,变量x
就属于 boolean 类型。
string 类型
string
类型包含所有字符串。
const x: string
上面示例中,变量x
就属于 string 类型。
number 类型
const x: number
上面示例中,变量x
就属于 number 类型。
联合类型
联合类型(union types)指的是多个类型组成的一个新类型,使用符号|
表示。
联合类型A|B
表示,任何一个类型只要属于A
或B
,就属于联合类型A|B
。
let x: string | number;
x = 123; // 正确
x = "abc"; // 正确
上面示例中,变量x
就是联合类型string|number
,表示它的值既可以是字符串,也可以是数值。
联合类型可以与值类型相结合,表示一个变量的值有若干种可能。
let setting: true | false;
let gender: "male" | "female";
let rainbowColor: "赤" | "橙" | "黄" | "绿" | "青" | "蓝" | "紫";
上面的示例都是由值类型组成的联合类型,非常清晰地表达了变量的取值范围。其中,true|false
其实就是布尔类型boolean
。
如果某个变量确实可能包含空值,就可以采用联合类型的写法。
let name: string | null;
name = "John";
name = null;
上面示例中,变量name
的值可以是字符串,也可以是null
。
如果一个变量有多种类型,读取该变量时,往往需要进行“类型缩小”(type narrowing),区分该值到底属于哪一种类型,然后再进一步处理。
function printId(id: number | string) {
console.log(id.toUpperCase()); // 报错
}
上面示例中,参数变量id
可能是数值,也可能是字符串,这时直接对这个变量调用toUpperCase()
方法会报错,因为这个方法只存在于字符串,不存在于数值。
解决方法就是对参数id
做一下类型缩小,确定它的类型以后再进行处理。
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
上面示例中,函数体内部会判断一下变量id
的类型,如果是字符串,就对其执行toUpperCase()
方法。
“类型缩小”是 TypeScript 处理联合类型的标准方法,凡是遇到可能为多种类型的场合,都需要先缩小类型,再进行处理。实际上,联合类型本身可以看成是一种“类型放大”(type widening),处理时就需要“类型缩小”(type narrowing)。
下面是“类型缩小”的另一个例子。
function getPort(scheme: "http" | "https") {
switch (scheme) {
case "http":
return 80;
case "https":
return 443;
}
}
上面示例中,函数体内部对参数变量scheme
进行类型缩小,根据不同的值类型,返回不同的结果。
type 命令
type
命令用来定义一个类型的别名。
type Age = number;
let age: Age
上面示例中,type
命令为number
类型定义了一个别名Age
。这样就能像使用number
一样,使用Age
作为类型。
别名可以让类型的名字变得更有意义,也能增加代码的可读性,还可以使复杂类型用起来更方便,便于以后修改变量的类型。