前言
在上一篇文章中,我们学习了TS
的三种特殊类型。这篇文章我们主要学习TS
的数组和元组。
数组
TS
数组有一个根本特征:所有成员的类型必须相同,但是成员数量是不确定的,可以是无限数量的成员,也可以是零成员。
写法
数组的类型有两种写法。
- 第一种写法是在数组成员的类型后面,加上一对方括号。
TS
let arr:number[] = [1, 2, 3];
上面示例中,数组arr
的类型是number[]
,其中number
表示数组成员类型是number
。
- 数组类型的第二种写法是使用 TypeScript 内置的 Array 接口。
TS
let arr:Array<number> = [1, 2, 3];
上面示例中,数组arr
的类型是Array<number>
,其中number
表示成员类型是number
。
使用
数组类型声明了以后,成员数量是不限制的,任意数量的成员都可以,也可以是空数组。
TS
let arr:number[];
arr = [];
arr = [1];
arr = [1, 2];
arr = [1, 2, 3];
数组增加成员或减少成员,都是可以的。
TS
let arr:number[] = [1, 2, 3];
arr[3] = 4;
arr.length = 2;
由于成员数量可以动态变化,所以TS
不会对数组边界进行检查,越界访问数组并不会报错。
TS
let arr:number[] = [1, 2, 3];
let Number = arr[3]; // 正确
上面示例中,变量Number
的值是一个不存在的数组成员,但是TS
并不会报错。
数组的类型推断
如果数组变量没有声明类型,TS
就会推断数组成员的类型。这时,推断行为会因为值的不同,而有所不同。
如果变量的初始值是空数组,那么TS
会推断数组类型是any[]
。
TS
// 推断为 any[]
const arr = [];
后面,为这个数组赋值时,TS
会自动更新类型推断。
TS
const arr = [];
arr // 推断为 any[]
arr.push(123);
arr // 推断类型为 number[]
arr.push('abc');
arr // 推断类型为 (string|number)[]
但是,类型推断的自动更新只发生初始值为空数组的情况。 如果初始值不是空数组,类型推断就不会更新。
TS
// 推断类型为 number[]
const arr = [123];
arr.push('abc'); // 报错
上面示例中,数组变量arr
的初始值是[123]
,TS
就推断成员类型为number
。新成员如果不是这个类型,TS
就会报错,而不会更新类型推断。
只读数组
JS
规定,const
命令声明的数组变量是可以改变成员的。
JS
const arr = [0, 1];
arr[0] = 2;
上面示例中,修改const
命令声明的数组的成员是允许的。
但是,很多时候确实有声明为只读数组的需求,即不允许变动数组成员。TS
允许声明只读数组,方法是在数组类型前面加上readonly
关键字。
TS
const arr:readonly number[] = [0, 1];
arr[1] = 2; // 报错
arr.push(3); // 报错
delete arr[0]; // 报错
TypeScript 将
readonly number[]
与number[]
视为两种不一样的类型,后者是前者的子类型。
我们知道,子类型继承了父类型的所有特征,并加上了自己的特征,所以子类型number[]
可以用于所有使用父类型的场合,反过来就不行。
TS
let a1:number[] = [0, 1];
let a2:readonly number[] = a1; // 正确
a1 = a2; // 报错
上面示例中,子类型number[]
可以赋值给父类型readonly number[]
,但是反过来就会报错。
const断言
只读数组还有一种声明方法,就是使用"const 断言"。
TS
const arr = [0, 1] as const;
arr[0] = [2]; // 报错
上面示例中,as const
告诉TS
,推断类型时要把变量arr
推断为只读数组,从而使得数组成员无法改变。
元组
元组(tuple)是TS
特有的数据类型,JS
没有单独区分这种类型。它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同。
元组的使用说明
由于成员的类型可以不一样,所以元组必须明确声明每个成员的类型。
TS
const s:[string, string, boolean]
= ['a', 'b', true];
上面示例中,元组s
的前两个成员的类型是string
,最后一个成员的类型是boolean
。
数组的成员类型写在方括号外面(number[]
),元组的成员类型是写在方括号里面([number]
)。TS
的区分方法就是,成员类型写在方括号里面的就是元组,写在外面的就是数组。
TS
// 数组
let a:number[] = [1];
// 元组
let t:[number] = [1];
使用元组时,必须明确给出类型声明(上例的[number]
),不能省略,否则TS
会把一个值自动推断为数组。
TS
// a 的类型被推断为 (number | boolean)[]
let a = [1, true];
元组成员的类型可以添加问号后缀(?
),表示该成员是可选的。 下面示例中,元组a
的第二个成员是可选的,可以省略。
TS
let a:[number, number?] = [1];
注意,问号只能用于元组的尾部成员,也就是说,所有可选成员必须在必选成员之后。
TS
type myTuple = [
number,
number,
number?,
string?
];
由于需要声明每个成员的类型,所以大多数情况下,元组的成员数量是有限的,从类型声明就可以明确知道,元组包含多少个成员,越界的成员会报错。
TS
let x:[string, string] = ['a', 'b'];
x[2] = 'c'; // 报错
只读元组
元组也可以是只读的,不允许修改,有两种写法。
TS
// 写法一
type t = readonly [number, string]
// 写法二
type t = Readonly<[number, string]>
上面示例中,两种写法都可以得到只读元组,其中写法二是一个泛型,用到了工具类型Readonly<T>
。跟数组一样,只读元组是元组的父类型。所以,元组可以替代只读元组,而只读元组不能替代元组。
下面示例中,类型t1
是只读元组,类型t2
是普通元组。t2
类型可以赋值给t1
类型,反过来就会报错。
TS
type t1 = readonly [number, number];
type t2 = [number, number];
let x:t2 = [1, 2];
let y:t1 = x; // 正确
x = y; // 报错