数组类型
数组是十分常用的数据结构,它表示一组有序元素的集合。在 TypeScript 中,数组值的数据类型为数组类型。
数组类型定义
TypeScript 提供了以下两种方式来定义数组类型:
-
简便数组类型表示法。
-
泛型数组类型表示法。
以上两种数组类型定义方式仅在编码风格上有所区别,两者在功能上没有任何差别。
简便数组类型表示法
简便数组类型表示法借用了数组字面量的语法,通过在数组元素类型之后添加一对方括号 “[]” 来定义数组类型。它的语法如下所示:
TElement[]
该语法中,TElement
代表数组元素的类型,方括号 []
代表数组类型。在 TElement
与 []
之间不允许出现换行符号。
下例中,我们使用 number[]
类型注解定义了常量 digits
的类型为 number
数组类型,它表示 digits
数组中元素的类型为 number
类型。示例如下:
const digits: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
如果数组中元素的类型为复合类型,则需要在数组元素类型上使用分组运算符,即小括号。例如,下例中的 red
数组既包含字符串元素也包含数字元素。因此,red
数组元素的类型为 string
类型和 number
类型构成的联合类型,即 string |number
。在使用简便数组类型表示法时,必须先将联合类型放在分组运算符内,然后再在后面添加一对方括号。示例如下:
const red: (string | number)[] = ['f', f, 0, 0, 0, 0];
此例中,若在类型注解里没有使用分组运算符,则表示 string
类型和 number[]
类型的联合类型,即 string | (number[])
。该类型与实际数组类型不兼容,因此将产生编译错误。示例如下:
const red: string | number[] = ['f', 'f', 0, 0, 0, 0];
// ~~~
// 编译错误
泛型数组类型表示法
泛型数组类型表示法是另一种表示数组类型的方法。顾名思义,泛型数组类型表示法就是使用泛型来表示数组类型。它的语法如下所示:
Array<TElement>
该语法中,Array
代表数组类型;<TElement>
是类型参数的语法,其中 TElement
代表数组元素的类型。关于泛型的详细介绍请参考 6.1 节。
下例中,我们使用 Array<number>
类型注解定义了常量 digits
的类型为 number
数组类型,它表示 digits
数组中元素的类型为 number
类型。示例如下:
const digits: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
在使用泛型数组类型表示法时,就算数组中元素的类型为复合类型也不需要使用分组运算符。我们还是以既包含字符串元素也包含数字元素的 red
数组为例,示例如下:
const red: Array<string | number> = ['f', 'f', 0, 0, 0, 0];
此例中,我们不再需要对联合类型 string | number
使用分组运算符。
两种方法比较
正如前文所讲,简便数组类型表示法和泛型数组类型表示法在功能上没有任何差别,两者只是在编程风格上有所差别。
在定义简单数组类型时,如数组元素为单一原始类型或类型引用,使用简便数组类型表示法更加清晰和简洁。示例如下:
let a: string[];
let b: HTMLButtonElement[];
如果数组元素是复杂类型,如对象类型和联合类型等,则可以选择使用泛型数组类型表示法。它也许能让代码看起来更加整洁一些。示例如下:
let a: Array<string | number>;
let b: Array<{ x: number; y: number }>;
总结起来,目前存在以下三种常见的编码风格供读者参考:
-
始终使用简便数组类型表示法。
-
始终使用泛型数组类型表示法。
-
当数组元素类型为单一原始类型或类型引用时,始终使用简便数组类型表示法;在其他情况下不做限制。
数组元素类型
在定义了数组类型之后,当访问数组元素时能够获得正确的元素类型信息。示例如下:
const digits: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const zero = digits[0];
// ~~~~
// number类型
此例中,虽然没有给常量 zero
添加类型注解,但是 TypeScript 编译器能够从数组类型中推断出 zero
的类型为 number
类型。
我们知道,当访问数组中不存在的元素时将返回 undefined
值。TypeScript 的类型系统无法推断出是否存在数组访问越界的情况,因此即使访问了不存在的数组元素,还是会得到声明的数组元素类型。示例如下:
const digits: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// 没有编译错误
const out: number = digits[100];
只读数组
只读数组与常规数组的区别在于,只读数组仅允许程序读取数组元素而不允许修改数组元素。
TypeScript 提供了以下三种方式来定义一个只读数组:
-
使用
ReadonlyArray<T>
内置类型。 -
使用
readonly
修饰符。 -
使用
Readonly<T>
工具类型。
以上三种定义只读数组的方式只是语法不同,它们在功能上没有任何差别。
ReadonlyArray<T>
在 TypeScript 早期版本中,提供了 ReadonlyArray<T>
类型专门用于定义只读数组。在该类型中,类型参数 T
表示数组元素的类型。示例如下:
const red: ReadonlyArray<number> = [255, 0, 0];
此例中,定义了只读数组 red
,该数组中元素的类型为 number
。
readonly
TypeScript 3.4 版本中引入了一种新语法,使用 readonly
修饰符能够定义只读数组。在定义只读数组时,将 readonly
修饰符置于数组类型之前即可。示例如下:
const red: readonly number[] = [255, 0, 0];
注意,readonly
修饰符不允许与泛型数组类型表示法一起使用。示例如下:
const red: readonly Array<number> = [255, 0, 0];
// ~~~~~~~~
// 编译错误
Readonly<T>
Readonly<T>
是 TypeScript 提供的一个内置工具类型,用于定义只读对象类型。该工具类型能够将类型参数 T
的所有属性转换为只读属性,它的定义如下所示:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
由于 TypeScript 3.4 支持了使用 readonly
修饰符来定义只读数组,所以从 TypeScript 3.4 开始可以使用 Readonly<T>
工具类型来定义只读数组。示例如下:
const red: Readonly<number[]> = [255, 0, 0];
需要注意的是,类型参数 T
的值为数组类型 number[]
,而不是数组元素类型 number
。在这一点上,它与 ReadonlyArray<T>
类型是有区别的。
注意事项
我们可以通过数组元素索引来访问只读数组元素,但是不能修改只读数组元素。示例如下:
const red: readonly number[] = [255, 0, 0];
red[0]; // 正确
red[0] = 0; // 编译错误
在只读数组上也不支持任何能够修改数组元素的方法,如 push
和 pop
方法等。示例如下:
const red: readonly number[] = [255, 0, 0];
red.push(0); // 编译错误
red.pop(); // 编译错误
在进行赋值操作时,允许将常规数组类型赋值给只读数组类型,但是不允许将只读数组类型赋值给常规数组类型。换句话说,不能通过赋值操作来放宽对只读数组的约束。示例如下:
const a: number[] = [0];
const ra: readonly number[] = [0];
const x: readonly number[] = a; // 正确
const y: number[] = ra; // 编译错误