Skip to content

TypeScript Array 数组

在 TypeScript 中,数组是一种特殊的数据类型,用于存储多个相同类型的元素。数组是 TypeScript 中最常用的数据结构之一,它提供了丰富的方法来操作和处理数据。本文将详细介绍 TypeScript 中的数组类型。

1. 基本用法

声明数组

TypeScript 中有两种方式来声明数组:

方式一:使用类型注解 + 方括号

typescript
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: string[] = ["apple", "banana", "cherry"];
let booleans: boolean[] = [true, false, true];

方式二:使用泛型数组类型

typescript
let numbers: Array<number> = [1, 2, 3, 4, 5];
let strings: Array<string> = ["apple", "banana", "cherry"];
let booleans: Array<boolean> = [true, false, true];

初始化数组

typescript
// 空数组
let emptyArray: number[] = [];

// 初始化数组
let numbers: number[] = [1, 2, 3, 4, 5];

// 使用 Array 构造函数
let numbers2: number[] = new Array(5); // 创建一个长度为 5 的空数组
let numbers3: number[] = new Array(1, 2, 3, 4, 5); // 创建包含元素的数组

// 使用 Array.from()
let numbers4: number[] = Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]

// 使用 Array.of()
let numbers5: number[] = Array.of(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

访问数组元素

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

console.log(numbers[0]); // 输出:1
console.log(numbers[1]); // 输出:2
console.log(numbers[numbers.length - 1]); // 输出:5

// 修改元素
numbers[0] = 10;
console.log(numbers[0]); // 输出:10

数组长度

typescript
let numbers: number[] = [1, 2, 3, 4, 5];
console.log(numbers.length); // 输出:5

// 修改长度
numbers.length = 3;
console.log(numbers); // 输出:[1, 2, 3]

numbers.length = 5;
console.log(numbers); // 输出:[1, 2, 3, undefined, undefined]

2. 数组类型注解

基本类型数组

typescript
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: string[] = ["apple", "banana", "cherry"];
let booleans: boolean[] = [true, false, true];
let nulls: null[] = [null, null, null];
let undefineds: undefined[] = [undefined, undefined, undefined];

对象类型数组

typescript
interface Person {
  name: string;
  age: number;
}

let people: Person[] = [
  { name: "John", age: 30 },
  { name: "Jane", age: 25 },
  { name: "Bob", age: 35 }
];

联合类型数组

typescript
let mixed: (string | number)[] = ["apple", 1, "banana", 2, "cherry", 3];

嵌套数组

typescript
let matrix: number[][] = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

let nested: Array<Array<number>> = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

元组类型(固定长度和类型的数组)

typescript
let tuple: [string, number, boolean] = ["apple", 10, true];

3. 数组方法

3.1 增删改查方法

push()

向数组末尾添加一个或多个元素,并返回新的长度。

typescript
let numbers: number[] = [1, 2, 3];
let length: number = numbers.push(4, 5);
console.log(numbers); // 输出:[1, 2, 3, 4, 5]
console.log(length); // 输出:5

pop()

从数组末尾移除一个元素,并返回该元素。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];
let last: number = numbers.pop();
console.log(numbers); // 输出:[1, 2, 3, 4]
console.log(last); // 输出:5

unshift()

向数组开头添加一个或多个元素,并返回新的长度。

typescript
let numbers: number[] = [3, 4, 5];
let length: number = numbers.unshift(1, 2);
console.log(numbers); // 输出:[1, 2, 3, 4, 5]
console.log(length); // 输出:5

shift()

从数组开头移除一个元素,并返回该元素。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];
let first: number = numbers.shift();
console.log(numbers); // 输出:[2, 3, 4, 5]
console.log(first); // 输出:1

splice()

添加、删除或替换数组中的元素。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

// 删除元素
let removed: number[] = numbers.splice(1, 2); // 从索引 1 开始删除 2 个元素
console.log(numbers); // 输出:[1, 4, 5]
console.log(removed); // 输出:[2, 3]

// 添加元素
numbers.splice(1, 0, 2, 3); // 从索引 1 开始,删除 0 个元素,添加 2 和 3
console.log(numbers); // 输出:[1, 2, 3, 4, 5]

// 替换元素
numbers.splice(1, 2, 6, 7); // 从索引 1 开始,删除 2 个元素,添加 6 和 7
console.log(numbers); // 输出:[1, 6, 7, 4, 5]

slice()

提取数组的一部分,返回新数组。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

let slice1: number[] = numbers.slice(1, 4); // 从索引 1 开始,到索引 4 结束(不包括 4)
console.log(slice1); // 输出:[2, 3, 4]

let slice2: number[] = numbers.slice(2); // 从索引 2 开始到结束
console.log(slice2); // 输出:[3, 4, 5]

let slice3: number[] = numbers.slice(-3); // 从倒数第 3 个元素开始到结束
console.log(slice3); // 输出:[3, 4, 5]

3.2 遍历方法

forEach()

遍历数组中的每个元素,执行回调函数。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

numbers.forEach((value, index, array) => {
  console.log(`Index ${index}: ${value}`);
});
// 输出:
// Index 0: 1
// Index 1: 2
// Index 2: 3
// Index 3: 4
// Index 4: 5

map()

遍历数组中的每个元素,执行回调函数,返回新数组。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

let doubled: number[] = numbers.map((value) => value * 2);
console.log(doubled); // 输出:[2, 4, 6, 8, 10]

let strings: string[] = numbers.map((value) => `Number: ${value}`);
console.log(strings); // 输出:["Number: 1", "Number: 2", "Number: 3", "Number: 4", "Number: 5"]

filter()

遍历数组中的每个元素,执行回调函数,返回符合条件的元素组成的新数组。

typescript
let numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let even: number[] = numbers.filter((value) => value % 2 === 0);
console.log(even); // 输出:[2, 4, 6, 8, 10]

let greaterThan5: number[] = numbers.filter((value) => value > 5);
console.log(greaterThan5); // 输出:[6, 7, 8, 9, 10]

reduce()

遍历数组中的每个元素,执行回调函数,将结果累积到一个值中。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

// 求和
let sum: number = numbers.reduce((accumulator, value) => accumulator + value, 0);
console.log(sum); // 输出:15

// 求积
let product: number = numbers.reduce((accumulator, value) => accumulator * value, 1);
console.log(product); // 输出:120

// 找出最大值
let max: number = numbers.reduce((accumulator, value) => Math.max(accumulator, value), -Infinity);
console.log(max); // 输出:5

reduceRight()

与 reduce() 类似,但从数组末尾开始遍历。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

let reversed: number[] = numbers.reduceRight((accumulator, value) => {
  accumulator.push(value);
  return accumulator;
}, [] as number[]);
console.log(reversed); // 输出:[5, 4, 3, 2, 1]

every()

检查数组中的所有元素是否都符合条件,返回布尔值。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

let allPositive: boolean = numbers.every((value) => value > 0);
console.log(allPositive); // 输出:true

let allEven: boolean = numbers.every((value) => value % 2 === 0);
console.log(allEven); // 输出:false

some()

检查数组中是否有至少一个元素符合条件,返回布尔值。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

let hasEven: boolean = numbers.some((value) => value % 2 === 0);
console.log(hasEven); // 输出:true

let hasNegative: boolean = numbers.some((value) => value < 0);
console.log(hasNegative); // 输出:false

find()

返回数组中第一个符合条件的元素,如果没有则返回 undefined。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

let firstEven: number | undefined = numbers.find((value) => value % 2 === 0);
console.log(firstEven); // 输出:2

let firstNegative: number | undefined = numbers.find((value) => value < 0);
console.log(firstNegative); // 输出:undefined

findIndex()

返回数组中第一个符合条件的元素的索引,如果没有则返回 -1。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

let firstEvenIndex: number = numbers.findIndex((value) => value % 2 === 0);
console.log(firstEvenIndex); // 输出:1

let firstNegativeIndex: number = numbers.findIndex((value) => value < 0);
console.log(firstNegativeIndex); // 输出:-1

findLast()

返回数组中最后一个符合条件的元素,如果没有则返回 undefined。

typescript
let numbers: number[] = [1, 2, 3, 4, 5, 6];

let lastEven: number | undefined = numbers.findLast((value) => value % 2 === 0);
console.log(lastEven); // 输出:6

findLastIndex()

返回数组中最后一个符合条件的元素的索引,如果没有则返回 -1。

typescript
let numbers: number[] = [1, 2, 3, 4, 5, 6];

let lastEvenIndex: number = numbers.findLastIndex((value) => value % 2 === 0);
console.log(lastEvenIndex); // 输出:5

3.3 其他方法

concat()

连接两个或多个数组,返回新数组。

typescript
let arr1: number[] = [1, 2, 3];
let arr2: number[] = [4, 5, 6];
let arr3: number[] = [7, 8, 9];

let combined: number[] = arr1.concat(arr2, arr3);
console.log(combined); // 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]

join()

将数组中的元素连接成一个字符串,返回该字符串。

typescript
let fruits: string[] = ["apple", "banana", "cherry"];

let joined: string = fruits.join(", ");
console.log(joined); // 输出:"apple, banana, cherry"

let joinedWithDash: string = fruits.join("-");
console.log(joinedWithDash); // 输出:"apple-banana-cherry"

reverse()

反转数组中的元素,修改原数组。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];
numbers.reverse();
console.log(numbers); // 输出:[5, 4, 3, 2, 1]

sort()

排序数组中的元素,修改原数组。

typescript
let numbers: number[] = [3, 1, 4, 1, 5, 9, 2, 6];

// 默认排序(按字符串顺序)
numbers.sort();
console.log(numbers); // 输出:[1, 1, 2, 3, 4, 5, 6, 9]

// 自定义排序
numbers.sort((a, b) => b - a); // 降序
console.log(numbers); // 输出:[9, 6, 5, 4, 3, 2, 1, 1]

includes()

检查数组是否包含指定元素,返回布尔值。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

let has3: boolean = numbers.includes(3);
console.log(has3); // 输出:true

let has6: boolean = numbers.includes(6);
console.log(has6); // 输出:false

// 从指定索引开始搜索
let has3FromIndex2: boolean = numbers.includes(3, 2);
console.log(has3FromIndex2); // 输出:true

let has3FromIndex4: boolean = numbers.includes(3, 4);
console.log(has3FromIndex4); // 输出:false

indexOf()

返回指定元素在数组中第一次出现的索引,如果没有则返回 -1。

typescript
let numbers: number[] = [1, 2, 3, 4, 3, 5];

let index: number = numbers.indexOf(3);
console.log(index); // 输出:2

let notFound: number = numbers.indexOf(6);
console.log(notFound); // 输出:-1

// 从指定索引开始搜索
let indexFrom2: number = numbers.indexOf(3, 3);
console.log(indexFrom2); // 输出:4

lastIndexOf()

返回指定元素在数组中最后一次出现的索引,如果没有则返回 -1。

typescript
let numbers: number[] = [1, 2, 3, 4, 3, 5];

let lastIndex: number = numbers.lastIndexOf(3);
console.log(lastIndex); // 输出:4

let notFound: number = numbers.lastIndexOf(6);
console.log(notFound); // 输出:-1

// 从指定索引开始搜索(从后向前)
let lastIndexFrom3: number = numbers.lastIndexOf(3, 3);
console.log(lastIndexFrom3); // 输出:2

fill()

用指定值填充数组中的元素,修改原数组。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

// 填充所有元素
numbers.fill(0);
console.log(numbers); // 输出:[0, 0, 0, 0, 0]

// 从指定索引开始填充
numbers.fill(1, 1);
console.log(numbers); // 输出:[0, 1, 1, 1, 1]

// 从指定索引开始,到指定索引结束填充
numbers.fill(2, 2, 4);
console.log(numbers); // 输出:[0, 1, 2, 2, 1]

flat()

将嵌套数组扁平化为指定深度的数组,返回新数组。

typescript
let nested: number[][] = [[1, 2], [3, 4], [5, 6]];

let flattened: number[] = nested.flat();
console.log(flattened); // 输出:[1, 2, 3, 4, 5, 6]

let deeplyNested: number[][][] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]];

let flattened2: number[] = deeplyNested.flat(2);
console.log(flattened2); // 输出:[1, 2, 3, 4, 5, 6, 7, 8]

flatMap()

先对数组中的每个元素执行 map(),然后对结果执行 flat(),返回新数组。

typescript
let numbers: number[] = [1, 2, 3];

let result: number[] = numbers.flatMap((value) => [value, value * 2]);
console.log(result); // 输出:[1, 2, 2, 4, 3, 6]

copyWithin()

将数组中的一部分元素复制到另一部分,修改原数组。

typescript
let numbers: number[] = [1, 2, 3, 4, 5];

// 从索引 0 开始,将索引 3 及以后的元素复制到索引 0 处
numbers.copyWithin(0, 3);
console.log(numbers); // 输出:[4, 5, 3, 4, 5]

// 从索引 1 开始,将索引 0 到 2 的元素复制到索引 1 处
numbers.copyWithin(1, 0, 2);
console.log(numbers); // 输出:[4, 4, 5, 4, 5]

entries()

返回一个包含数组所有键值对的迭代器。

typescript
let fruits: string[] = ["apple", "banana", "cherry"];

for (const [index, value] of fruits.entries()) {
  console.log(`${index}: ${value}`);
}
// 输出:
// 0: apple
// 1: banana
// 2: cherry

keys()

返回一个包含数组所有索引的迭代器。

typescript
let fruits: string[] = ["apple", "banana", "cherry"];

for (const index of fruits.keys()) {
  console.log(index);
}
// 输出:
// 0
// 1
// 2

values()

返回一个包含数组所有值的迭代器。

typescript
let fruits: string[] = ["apple", "banana", "cherry"];

for (const value of fruits.values()) {
  console.log(value);
}
// 输出:
// apple
// banana
// cherry

4. 类型守卫

在 TypeScript 中,可以使用类型守卫来检查一个值是否是数组类型。

Array.isArray() 类型守卫

typescript
function processValue(value: any) {
  if (Array.isArray(value)) {
    // 在这个分支中,TypeScript 知道 value 是数组类型
    console.log(`Array length: ${value.length}`);
    value.forEach((item: any) => console.log(item));
  } else {
    console.log(`Not an array: ${value}`);
  }
}

processValue([1, 2, 3]); // 输出:Array length: 3  1  2  3
processValue("hello"); // 输出:Not an array: hello
processValue(123); // 输出:Not an array: 123

自定义类型守卫

typescript
function isNumberArray(value: any): value is number[] {
  return Array.isArray(value) && value.every((item) => typeof item === "number");
}

function processValue(value: any) {
  if (isNumberArray(value)) {
    // 在这个分支中,TypeScript 知道 value 是 number[] 类型
    let sum = value.reduce((acc: number, num: number) => acc + num, 0);
    console.log(`Sum: ${sum}`);
  } else {
    console.log(`Not a number array: ${value}`);
  }
}

processValue([1, 2, 3]); // 输出:Sum: 6
processValue(["1", "2", "3"]); // 输出:Not a number array: 1,2,3
processValue("hello"); // 输出:Not a number array: hello

5. 数组操作的最佳实践

1. 使用类型注解

为数组添加类型注解可以提高代码的可读性和类型安全性。

typescript
// 推荐
let numbers: number[] = [1, 2, 3, 4, 5];

// 不推荐
let numbers = [1, 2, 3, 4, 5]; // 类型推断为 number[],但不如显式注解清晰

2. 使用 const 声明只读数组

对于不需要修改的数组,使用 const 声明可以防止意外修改。

typescript
// 推荐
const numbers: number[] = [1, 2, 3, 4, 5];

// 不推荐
let numbers: number[] = [1, 2, 3, 4, 5]; // 可能被意外修改

3. 使用函数式方法

对于数组操作,优先使用函数式方法(如 map、filter、reduce 等),而不是命令式方法(如 for 循环)。

typescript
// 推荐
let numbers: number[] = [1, 2, 3, 4, 5];
let doubled: number[] = numbers.map((num) => num * 2);

// 不推荐
let numbers: number[] = [1, 2, 3, 4, 5];
let doubled: number[] = [];
for (let i = 0; i < numbers.length; i++) {
  doubled.push(numbers[i] * 2);
}

4. 注意数组方法的返回值

许多数组方法返回新数组,而不是修改原数组。

typescript
// 推荐
let numbers: number[] = [1, 2, 3, 4, 5];
let doubled: number[] = numbers.map((num) => num * 2);

// 不推荐
let numbers: number[] = [1, 2, 3, 4, 5];
numbers.map((num) => num * 2); // 原数组不变,返回值被忽略

5. 避免使用 delete 操作符

使用 delete 操作符删除数组元素会留下 undefined 占位符,推荐使用 splice() 方法。

typescript
// 推荐
let numbers: number[] = [1, 2, 3, 4, 5];
numbers.splice(2, 1); // 删除索引 2 处的元素
console.log(numbers); // 输出:[1, 2, 4, 5]

// 不推荐
let numbers: number[] = [1, 2, 3, 4, 5];
delete numbers[2]; // 删除索引 2 处的元素
console.log(numbers); // 输出:[1, 2, undefined, 4, 5]

6. 合理使用数组方法的参数

了解数组方法的参数含义,避免使用错误的参数。

typescript
// 推荐:正确使用 slice() 方法
let numbers: number[] = [1, 2, 3, 4, 5];
let slice: number[] = numbers.slice(1, 4); // 从索引 1 开始,到索引 4 结束(不包括 4)
console.log(slice); // 输出:[2, 3, 4]

// 不推荐:错误使用 slice() 方法
let numbers: number[] = [1, 2, 3, 4, 5];
let slice: number[] = numbers.slice(1, 3); // 期望得到 [2, 3, 4],但实际得到 [2, 3]

7. 注意数组的长度

修改数组的长度可能会导致意外的行为,特别是当长度小于原长度时。

typescript
// 注意:修改数组长度可能会导致数据丢失
let numbers: number[] = [1, 2, 3, 4, 5];
numbers.length = 3; // 截断数组
console.log(numbers); // 输出:[1, 2, 3]

// 注意:增加数组长度会添加 undefined 元素
numbers.length = 5;
console.log(numbers); // 输出:[1, 2, 3, undefined, undefined]

6. 常见错误

1. 类型不匹配

在 TypeScript 中,数组元素的类型必须与数组的类型注解匹配。

typescript
// 错误:类型不匹配
let numbers: number[] = [1, 2, "3", 4, 5]; // 类型 'string' 不能赋值给类型 'number'

// 正确
let numbers: number[] = [1, 2, 3, 4, 5];
let mixed: (number | string)[] = [1, 2, "3", 4, 5];

2. 访问越界索引

访问数组中不存在的索引会返回 undefined。

typescript
let numbers: number[] = [1, 2, 3];
console.log(numbers[5]); // 输出:undefined

3. 忘记使用返回值

许多数组方法返回新数组,而不是修改原数组。

typescript
// 错误:忘记使用返回值
let numbers: number[] = [1, 2, 3, 4, 5];
numbers.map((num) => num * 2); // 原数组不变
console.log(numbers); // 输出:[1, 2, 3, 4, 5]

// 正确:使用返回值
let numbers: number[] = [1, 2, 3, 4, 5];
let doubled: number[] = numbers.map((num) => num * 2);
console.log(doubled); // 输出:[2, 4, 6, 8, 10]

4. 混淆修改原数组和返回新数组的方法

一些数组方法会修改原数组,而另一些会返回新数组。

typescript
// 修改原数组的方法:push, pop, unshift, shift, splice, reverse, sort, fill, copyWithin
let numbers: number[] = [1, 2, 3, 4, 5];
numbers.push(6);
console.log(numbers); // 输出:[1, 2, 3, 4, 5, 6]

// 返回新数组的方法:slice, concat, map, filter, reduce, flat, flatMap
let numbers: number[] = [1, 2, 3, 4, 5];
let sliced: number[] = numbers.slice(1, 4);
console.log(numbers); // 输出:[1, 2, 3, 4, 5](原数组不变)
console.log(sliced); // 输出:[2, 3, 4](新数组)

5. 错误使用 sort() 方法

sort() 方法默认按字符串顺序排序,对于数字排序需要提供比较函数。

typescript
// 错误:默认按字符串排序
let numbers: number[] = [10, 2, 5, 1, 8];
numbers.sort();
console.log(numbers); // 输出:[1, 10, 2, 5, 8](错误的排序)

// 正确:提供比较函数
let numbers: number[] = [10, 2, 5, 1, 8];
numbers.sort((a, b) => a - b); // 升序
console.log(numbers); // 输出:[1, 2, 5, 8, 10](正确的排序)

6. 错误使用 reduce() 方法

reduce() 方法需要提供初始值,特别是当数组可能为空时。

typescript
// 错误:没有提供初始值,当数组为空时会报错
let numbers: number[] = [];
let sum: number = numbers.reduce((acc, num) => acc + num); // 运行时错误:Reduce of empty array with no initial value

// 正确:提供初始值
let numbers: number[] = [];
let sum: number = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 输出:0

总结

TypeScript 中的数组是一种强大的数据结构,用于存储多个相同类型的元素。它包括:

  • 基本用法:声明和初始化数组,访问数组元素,修改数组长度
  • 类型注解:基本类型数组,对象类型数组,联合类型数组,嵌套数组,元组类型
  • 数组方法:增删改查方法,遍历方法,其他方法
  • 类型守卫:使用 Array.isArray() 和自定义类型守卫检查数组类型
  • 最佳实践:使用类型注解,使用 const 声明只读数组,使用函数式方法,注意数组方法的返回值,避免使用 delete 操作符,合理使用数组方法的参数,注意数组的长度
  • 常见错误:类型不匹配,访问越界索引,忘记使用返回值,混淆修改原数组和返回新数组的方法,错误使用 sort() 方法,错误使用 reduce() 方法

通过合理使用数组和相关方法,你可以在 TypeScript 中高效地处理和操作数据,编写更清晰、更安全的代码。