TypeScript 的数组类型

简介

JavaScript 数组在 TypeScript 里面分成两种类型,分别是:

  • 数组(array)

  • 元组(tuple)

TypeScript 数组有一个根本特征:所有成员的类型必须相同,但是成员数量是不确定的,可以是无限数量的成员,也可以是零成员。

数组的类型有两种写法。第一种写法是在数组成员的类型后面,加上一对方括号。

let arr: number[] = [1, 2, 3];

上面示例中,数组arr的类型是number[],其中number表示数组成员类型是number。

如果数组成员的类型比较复杂,可以写在圆括号里面。

let arr: (number | string)[];

上面示例中,数组arr的成员类型是number|string。

这个例子里面的圆括号是必须的,否则因为竖杠|的优先级低于[],TypeScript 会把number|string[]理解成number和string[]的联合类型。

如果数组成员可以是任意类型,写成any[]。当然,这种写法是应该避免的。

let arr: any[];

数组类型的第二种写法是使用 TypeScript 内置的 Array 接口。

let arr: Array<number> = [1, 2, 3];

上面示例中,数组arr的类型是Array,其中number表示成员类型是number。

这种写法对于成员类型比较复杂的数组,代码可读性会稍微好一些。

let arr: Array<number | string>;

这种写法本质上属于泛型,这里只要知道怎么写就可以了,详细解释参见《泛型》一章。另外,数组类型还有第三种写法,因为很少用到,本章就省略了,详见《interface 接口》一章。

数组类型声明了以后,成员数量是不限制的,任意数量的成员都可以,也可以是空数组。

let arr: number[];
arr = [];
arr = [1];
arr = [1, 2];
arr = [1, 2, 3];

上面示例中,数组arr无论有多少个成员,都是正确的。

这种规定的隐藏含义就是,数组的成员是可以动态变化的。

let arr: number[] = [1, 2, 3];

arr[3] = 4;
arr.length = 2;

arr; // [1, 2]

上面示例中,数组增加成员或减少成员,都是可以的。

正是由于成员数量可以动态变化,所以 TypeScript 不会对数组边界进行检查,越界访问数组并不会报错。

let arr: number[] = [1, 2, 3];
let foo = arr[3]; // 正确

上面示例中,变量foo的值是一个不存在的数组成员,TypeScript 并不会报错。

TypeScript 允许使用方括号读取数组成员的类型。

type Names = string[];
type Name = Names[0]; // string

上面示例中,类型Names是字符串数组,那么Names[0]返回的类型就是string。

由于数组成员的索引类型都是number,所以读取成员类型也可以写成下面这样。

type Names = string[];
type Name = Names[number]; // string

上面示例中,Names[number]表示数组Names所有数值索引的成员类型,所以返回string。

数组的类型推断

如果数组变量没有声明类型,TypeScript 就会推断数组成员的类型。这时,推断行为会因为值的不同,而有所不同。

如果变量的初始值是空数组,那么 TypeScript 会推断数组类型是any[]。

// 推断为 any[]
const arr = [];

后面,为这个数组赋值时,TypeScript 会自动更新类型推断。

const arr = [];
arr; // 推断为 any[]

arr.push(123);
arr; // 推断类型为 number[]

arr.push("abc");
arr; // 推断类型为 (string|number)[]

上面示例中,数组变量arr的初始值是空数组,然后随着新成员的加入,TypeScript 会自动修改推断的数组类型。

但是,类型推断的自动更新只发生初始值为空数组的情况。如果初始值不是空数组,类型推断就不会更新。

只读数组,const 断言

JavaScript 规定,const命令声明的数组变量是可以改变成员的。

const arr = [0, 1];
arr[0] = 2;

上面示例中,修改const命令声明的数组的成员是允许的。

但是,很多时候确实有声明为只读数组的需求,即不允许变动数组成员。

TypeScript 允许声明只读数组,方法是在数组类型前面加上readonly关键字。

const arr: readonly number[] = [0, 1];


arr[1] = 2; // 报错
arr.push(3); // 报错
delete arr[0]; // 报错

上面示例中,arr是一个只读数组,删除、修改、新增数组成员都会报错。

TypeScript 将readonly number[]与number[]视为两种不一样的类型,后者是前者的子类型。

这是因为只读数组没有pop()、push()之类会改变原数组的方法,所以number[]的方法数量要多于readonly number[],这意味着number[]其实是readonly number[]的子类型。

我们知道,子类型继承了父类型的所有特征,并加上了自己的特征,所以子类型number[]可以用于所有使用父类型的场合,反过来就不行。

let a1: number[] = [0, 1];
let a2: readonly number[] = a1; // 正确

a1 = a2; // 报错

上面示例中,子类型number[]可以赋值给父类型readonly number[],但是反过来就会报错。

由于只读数组是数组的父类型,所以它不能代替数组。这一点很容易产生令人困惑的报错。

function getSum(s: number[]) {
  // ...
}

const arr: readonly number[] = [1, 2, 3];
getSum(arr); // 报错

上面示例中,函数getSum()的参数s是一个数组,传入只读数组就会报错。原因就是只读数组是数组的父类型,父类型不能替代子类型。这个问题的解决方法是使用类型断言getSum(arr as number[]),详见《类型断言》一章。

注意,readonly关键字不能与数组的泛型写法一起使用。

// 报错
const arr: readonly Array<number> = [0, 1];

上面示例中,readonly与数组的泛型写法一起使用,就会报错。

实际上,TypeScript 提供了两个专门的泛型,用来生成只读数组的类型。

const a1: ReadonlyArray<number> = [0, 1];

const a2: Readonly<number[]> = [0, 1];

上面示例中,泛型ReadonlyArray和Readonly<T[]>都可以用来生成只读数组类型。两者尖括号里面的写法不一样,Readonly<T[]>的尖括号里面是整个数组(number[]),而ReadonlyArray的尖括号里面是数组成员(number)。

只读数组还有一种声明方法,就是使用“const 断言”。

const arr = [0, 1] as const;

arr[0] = [2]; // 报错

上面示例中,as const告诉 TypeScript,推断类型时要把变量arr推断为只读数组,从而使得数组成员无法改变。

多维数组

TypeScript 使用T[][]的形式,表示二维数组,T是最底层数组成员的类型。

var multi: number[][] = [
  [1, 2, 3],
  [23, 24, 25],
];

上面示例中,变量multi的类型是number[][],表示它是一个二维数组,最底层的数组成员类型是number。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一小池勺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值