阮一峰《TypeScript 教程》学习笔记——数组
1. 一段话总结
TypeScript 中的数组与元组是两种不同类型,数组的根本特征为所有成员类型必须相同、成员数量不确定(可动态变化);其类型有两种主要写法,分别是“数组成员类型+[]”(如number[])和“Array<数组成员类型>”(如Array<number>),复杂成员类型需注意括号优先级;数组类型推断分两种情况,初始为空数组时推断为any[]且会随赋值动态更新,初始为非空数组时推断类型固定(新增不同类型成员报错);只读数组可通过readonly关键字、ReadonlyArray<T>/Readonly<T[]>泛型或const断言实现,且普通数组是只读数组的子类型(只读数组不能替代普通数组使用);多维数组以T[][]形式表示(T为最底层成员类型),同时 TypeScript 不检查数组边界,越界访问不会报错。
2. 思维导图

3. 详细总结
一、数组与元组的区分
TypeScript 中数组(array)和元组(tuple)是两种独立的集合类型,本章聚焦数组,核心差异如下:
- 数组:所有成员类型必须相同,成员数量不确定(可动态增减,支持0~无限个成员);
- 元组:成员类型可不同,成员数量固定(下章详细介绍)。
二、数组类型的两种核心写法
| 写法分类 | 语法格式 | 示例 | 注意事项 |
|---|---|---|---|
| 方括号写法 | 数组成员类型 + [] | let arr:number[] = [1,2,3] | 复杂类型(如联合类型)需加括号,避免优先级问题(如`(number |
| Array泛型写法 | Array<数组成员类型> | let arr:Array<number> = [1,2,3] | 复杂类型可读性更优(如`Array<number |
此外,数组支持任意类型成员,语法为any[],但不建议使用(会丢失 TypeScript 的类型检查优势);数组类型声明后,成员数量灵活,空数组([])、1个成员([1])、多个成员([1,2,3])均符合要求。
三、数组的类型推断规则
TypeScript 会根据数组初始值自动推断成员类型,推断行为分两种场景:
-
初始值为空数组
- 初始推断结果:
any[]; - 动态更新特性:后续为数组添加成员时,推断类型会自动更新;
- 示例:
const arr = []; // 推断为 any[] arr.push(123); // 推断类型更新为 number[] arr.push('abc');// 推断类型更新为 (number|string)[]
- 初始推断结果:
-
初始值为非空数组
- 初始推断结果:根据初始成员的类型确定(如初始值
[123]推断为number[]); - 固定特性:推断类型一旦确定,后续不能添加不同类型的成员(否则报错);
- 示例:
const arr = [123]; // 推断为 number[] arr.push('abc'); // 报错(不能添加 string 类型成员)
- 初始推断结果:根据初始成员的类型确定(如初始值
四、只读数组的实现与特性
只读数组即不允许修改、新增、删除成员的数组,实现方式及特性如下:
1. 四种实现方式
| 实现方式 | 语法格式 | 示例 |
|---|---|---|
| readonly关键字 | readonly + 数组类型 | const arr:readonly number[] = [0,1] |
| ReadonlyArray | ReadonlyArray<成员类型> | const arr:ReadonlyArray<number> = [0,1] |
| Readonly<T[]> | Readonly<数组类型> | const arr:Readonly<number[]> = [0,1] |
| const断言 | 数组值 + as const | const arr = [0,1] as const |
2. 关键特性
- 类型关系:普通数组(如
number[])是只读数组(如readonly number[])的子类型;原因是普通数组包含push()、pop()等修改方法,而只读数组没有,子类型继承父类型特性并新增自身特性; - 使用限制:
- 只读数组不能直接赋值给普通数组(如
let a1:number[] = a2,其中a2为readonly number[],会报错); - 只读数组不能作为普通数组参数传入函数(需用类型断言解决,如
getSum(arr as number[]));
- 只读数组不能直接赋值给普通数组(如
- 错误用法:
readonly不能与Array<T>直接结合(如const arr:readonly Array<number> = [0,1]会报错)。
五、多维数组
- 表示形式:以
T[][]格式表示,其中T是最底层数组成员的类型; - 示例:
var multi:number[][] = [[1,2,3], [23,24,25]]; // 二维数组,最底层成员类型为 number
六、数组的其他重要特性
- 成员动态变化:数组声明后可通过索引新增成员(如
arr[3] = 4)或修改长度减少成员(如arr.length = 2); - 无边界检查:TypeScript 不检查数组边界,越界访问不会报错(如
let foo = arr[3]在arr = [1,2,3]中取值,语法正确但值为undefined); - 成员类型读取:可通过
Names[0]或Names[number]读取数组成员类型(如type Names = string[],则Names[0]和Names[number]均为string)。
4. 关键问题
问题1:TypeScript 数组的两种主要类型写法在复杂成员类型场景下,为何推荐 Array 泛型写法?
答案:两种主要写法分别是“成员类型+[]”和“Array<成员类型>”。在复杂成员类型(如联合类型number|string)场景下,“成员类型+[]”需额外添加括号(如(number|string)[]),否则 TypeScript 会因|优先级低于[],将number|string[]误解为number和string[]的联合类型;而 Array 泛型写法(Array<number|string>)无需处理括号优先级,代码可读性更优,因此推荐使用。
问题2:为何普通数组能赋值给只读数组,而只读数组不能赋值给普通数组?两者的类型关系是什么?
答案:两者的类型关系是普通数组(如number[])是只读数组(如readonly number[])的子类型。子类型的核心特征是“继承父类型所有特性,并新增自身特性”:只读数组仅包含读取成员的方法,普通数组在此基础上新增了push()、pop()等修改成员的方法,因此普通数组具备只读数组的所有能力,可赋值给只读数组;反之,只读数组缺少普通数组的修改方法,不具备普通数组的完整能力,因此不能赋值给普通数组。
问题3:TypeScript 数组的类型推断在初始值为空数组和非空数组时,行为差异的本质原因是什么?
答案:差异的本质原因是初始值是否能确定成员类型边界:
- 初始值为空数组时,无任何成员可用于确定类型,因此 TypeScript 先推断为
any[],并允许后续通过添加成员动态更新类型(逐步确定边界); - 初始值为非空数组时,已有成员可直接确定类型边界(如
[123]的成员类型边界为number),因此 TypeScript 推断类型后会固定边界,不允许后续添加超出边界的类型(如string),否则违反类型一致性原则。
