数组

在程序设计中,为了方便处理,数组是把具有相同类型的若干元素按有序的形式组织起来的一种形式。它用于在单一变量中存储多个值,并可以进行集中访问或修改。组成数组的各个值称为数组的元素,一个数组可以由零到任意个元素组成。

数组的声明与读写

TypeScript 中,数组有以下两种声明方式。它们仅存在编程风格上的差异,可以任选其一。

let 变量名称: 类型[];      //默认方式,在类型后面加上方括号[]表示数组
let 变量名称: Array<类型>; //泛型方式

在声明数组时,为数组赋初值,示例代码如下。

//字符串数组,包含a、b、c、d这4个元素
let array1: string[] = ["a", "b", "c", "d"];
//数值数组,包含1、2、3、4、5这5个元素
let array2: number[] = [1, 2, 3, 4, 5];
//数组没有任何元素,表示空数组
let array3: string[] = [];

利用索引来引用某个数组元素,对其进行读写操作。需要注意的是,数组元素的索引从 0 开始,例如,[0] 用于取数组中的第一个元素,[1] 用于取第二个。示例代码如下。

let array1: string[] = ["a", "b", "c", "d"];
//以下语句读取数组中的第一个元素,并将其赋给变量char
let char = array1[0];
console.log(char);
//以下语句将数组中的第2个元素由"b"修改为"x"
array1[1] = "x";
//以下语句输出['a', 'x', 'c', 'd']
console.log(array1);

通过数组的 length 属性获取数组的长度,并用它来执行特定的操作,示例代码如下。

let array1: string[] = ["a", "b", "c", "d"];
//以下语句输出4
console.log(array1.length);
//以下语句输出数组的最后一个元素,即d
console.log(array1[array1.length - 1]);

数组的遍历

使用循环语句,以数组的 length 属性作为循环次数遍历数组的各个元素,示例代码如下。

//遍历数组,循环体会执行4次,分别输出"a", "b", "c", "d"
for (let i = 0; i < array1.length; i++) {
    console.log(array1[i]);
}

使用 for…in 循环语句也可以遍历数组中的每个元素,语法如下。

for (let 临时存放单个索引的变量名称 in 数组名称) {
    //要执行的代码块
}

例如,以下代码遍历并输出每个元素的值。

let array1: string[] = ["a", "b", "c", "d"];
for (let x in array1) {
    console.log(array1[x]);
}

for…in 循环语句也支持 breakcontinue 关键字,用来跳出整个循环或跳过单个循环。

数组的方法

使用数组的内置方法可以操作数组,这些内置方法可以分为 6 个类别——将数组转换为字符串、添加/移除元素、查找元素位置、数组排序、数组裁剪与合并、数组迭代与筛选。接下来,一一进行介绍。

  1. 将数组转换为字符串

    表7-1列出了将数组转换为字符串的方法。

    image 2024 02 18 09 42 29 363
    Figure 1. 表7-1 将数组转换为字符串的方法

    示例代码如下。

    let array1: string[] = ["a", "b", "c"];
    console.log(array1.toString()); //输出"a,b,c"
    console.log(array1.join("-"));  //输出"a-b-c"
  2. 添加/移除元素

    表7-2列出了添加/移除元素的方法。

    image 2024 02 18 09 44 14 218
    Figure 2. 表7-2 添加/移除元素的方法

    示例代码如下。

    let array1 = ["b", "c", "d"];
    array1.unshift("a"); //数组此时为["a","b","c","d"]
    array1.push("e");    //数组此时为["a","b","c","d","e"]
    console.log(array1);
    array1.shift();      //数组此时为["b","c","d","e"]
    console.log(array1);
    array1.pop();        //数组此时为["b","c","d"]
    console.log(array1);

    注意,使用 push()unshift() 方法可以将多个元素值添加到数组中,示例代码如下。

    let array1 = ["b", "c", "d"];
    //以下代码执行后,数组为['1', '2', '3', 'b', 'c']
    array1.unshift("1","2","3");
    //以下代码执行后,数组为['1', '2', '3', 'b', 'c', 'd', 'e', 'f', 'g']
    array1.push("e","f","g");
  3. 查找元素位置

    表7-3中列出了查找元素位置的方法。

    image 2024 02 18 09 46 08 778
    Figure 3. 表7-3 查找元素位置的方法

    示例代码如下。

    let array1: string[] = ["a", "b", "c"];
    console.log(array1.indexOf("c"));     //输出2
    console.log(array1.indexOf("d"));     //没找到,输出-1
    console.log(array1.lastIndexOf("c")); //输出2
    console.log(array1.lastIndexOf("d"))  //没找到,输出-1
    //以下自定义函数用于判断当前元素值是否大于10
    function myFunction(value, index, array) {
        return value > 10;
    }
    let numbers: number[] = [4, 7, 9, 11, 15, 20];
    //首个匹配的值是11, 以下代码输出3
    console.log(numbers.findIndex(myFunction));
    let numbers2: number[] = [1, 2, 3];
    //没有匹配的值,以下代码输出-1
    console.log(numbers2.findIndex(myFunction));
  4. 数组排序

    表7-4列出了对数组排序的方法。

    image 2024 02 18 09 47 28 445
    Figure 4. 表7-4 数组排序的方法

    示例代码如下。

    let array1:string[] = ["a","x","c"];
    array1.reverse();
    console.log(array1); //输出['c', 'x', 'a']
    array1.sort();
    console.log(array1); //输出['a', 'c', 'x']

    由于 sort() 方法会按字母顺序排序,因此如果将其用于数字排序,结果可能不符合预期效果,示例代码如下。

    let array2:number[] = [2, 5, 37, 11];
    array2.sort();
    console.log(array2); //输出[11, 2, 37, 5]

    出现上述情况的原因在于数字会先转换为字符串,然后再进行排序,而字符串的大小规则为 1xxx<2xxx<3xxx<5xxx,因此会排出 11, 2, 37, 5 这样的顺序。如果要按照数字大小排序,就需要编写自定义排序函数,示例代码如下。

    function myFunction(value, nextValue){
        return value - nextValue;
    }
    let array2:number[] = [2, 5, 37, 11];
    array2.sort(myFunction);
    console.log(array2); //输出[2, 5, 11, 37]
  5. 数组裁剪与合并

    表7-5列出了对数组裁剪与合并的方法。

    image 2024 02 18 09 51 26 839
    Figure 5. 表7-5 对数组裁剪与合并的方法

    splice() 方法的应用示例如下。

    let array1 = ["a", "b", "e", "f"];
    //以下语句从索引2开始,移除0个元素,并插入"c"和"d"
    array1.splice(2, 0, "c", "d");
    //以下语句输出['a', 'b', 'c', 'd', 'e', 'f']
    console.log(array1);
    //以下语句从索引3开始,移除两个元素,不插入任何元素
    array1.splice(3, 2);
    //以下语句输出['a', 'b', 'c', 'f']
    console.log(array1);

    concat() 方法的应用示例如下。

    let array1 = ["a", "b"];
    let array2 = ["c", "d"];
    let array3 = array1.concat(array2);
    console.log(array1); //原来的数组不会改变,输出['a', 'b']
    console.log(array3); //输出['a', 'b', 'c', 'd']
    
    let array4 = array1.concat(array2, array3);
    console.log(array4); //输出['a', 'b', 'c', 'd', 'a', 'b', 'c', 'd']

    slice() 方法的应用示例如下。

    let array1 = ["a","b","c","d","e"];
    let array2 = array1.splice(1); //从索引1开始裁剪
    console.log(array1); //输出['a']
    console.log(array2); //输出['b', 'c', 'd', 'e']

    reduce()reduceRight() 只遍历顺序不同而已,其余规则一致,因此只介绍 reduce() 即可,应用示例如下。

    function myFunction(total, value, index, array) {
        return total + value;
    }
    
    var array1 = [5, 11, 23, 9];
    var sum = array1.reduce(myFunction);
    console.log(sum); //输出48
  6. 数组筛选与迭代

表7-6列出了用于数组筛选与迭代的方法。

image 2024 02 18 10 18 28 122
Figure 6. 表7-6 用于数组筛选与迭代的方法

forEach() 方法的应用示例如下。

function myFunction(value, index, array) {
    console.log(`当前值为${value},索引号为${index},遍历进度为${index + 1}/${array.length}`);
}
let array1 = ["a", "b", "c"];
array1.forEach(myFunction);

输出结果如下。

> 当前值为a,索引号为0,遍历进度为1/3
> 当前值为b,索引号为1,遍历进度为2/3
> 当前值为c,索引号为2,遍历进度为3/3

map()forEach() 方法很相似,但它的主要作用是产生新数组。自定义函数必须有返回值,并将该数组作为新数组的元素。例如,以下代码将字符串数组转换为数值数组。

function myFunction(value, index, array) {
    return Number(value);
}
let array1:string[] = ["111", "222", "333"];
let array2:number[] = array1.map(myFunction);
console.log(array2); //输出[111, 222, 333]

filter()find() 方法的主要区别在于 filter() 方法返回数组,即使符合条件的元素只有一个,也以数组的形式返回,而 find() 方法只返回首个匹配元素。它们的应用示例如下。

function myFunction(value, index, array) {
    return value > 10;
}
let numbers = [4, 7, 9, 11, 15, 20];

console.log(numbers.filter(myFunction)); //输出[11, 15, 20]
console.log(numbers.find(myFunction));   //输出11

every()some() 方法的主要区别在 every() 判断各个元素是否全部满足条件,some() 判断各个元素是否部分满足条件。它们的应用示例如下。

function myFunc(value, index, array) {
    return value > 10;
}
let numbers1 = [9, 10, 11];
let numbers2 = [100, 200, 30];
let numbers3 = [1, 2, 3];

console.log(numbers1.every(myFunc)); //部分满足,every输出false
console.log(numbers1.some(myFunc));  //部分满足,some输出true

console.log(numbers2.every(myFunc)); //全部满足,every输出true
console.log(numbers2.some(myFunc));  //全部满足,some输出true

console.log(numbers3.every(myFunc)); //全不满足,every输出false
console.log(numbers3.some(myFunc));  //全不满足,some输出false

只读数组

第 6 章提到,const 关键字只能限定栈上的内容不可编辑,而数组是引用类型,其数据存储在堆上,因此 const 关键字无法有效地限定数组为只读数组。要限定数组只能读取不可编辑,就需要使用 readonly 关键字。

声明方式有以下两种,它们仅在编程风格上有一些差异,可以任选其一。

let 变量名称:readonly 类型[] = [初始值1,初始值2,...,初始值n];
let 变量名称:ReadonlyArray<类型> = [初始值1,初始值2,...,初始值n];

示例代码如下,其中定义一个名为 array1 的只读数组。对数组进行编辑,会引起编译错误。

let array1: readonly string[] = ["a", "b", "c"];
//可以读取,以下代码输出"b"
console.log(array1[1]);
//编译错误:类型"readonly string[]"上不存在属性"push"。ts(2339)
array1.push("d");
//编译错误:类型"readonly string[]"中的索引签名仅允许读取。ts(2542)
array1[1] = "x";

多维数组

TypeScript 中,我们可以定义多维数组,多维数组即数组的元素也是数组。例如,以下代码分别定义了一个二维字符串数组和一个三维数值数组。

let array1: string[][] = [["a", "b"], ["x", "y"]];
let array2: number[][][] = [[[1, 2], [7, 8]], [[100, 101], [700, 701]]];

在编程中通常并不建议使用多维数组,因为这会大大降低代码的可读性和可维护性。