TypeScript学习笔记(三) 数组

大家好,我是半虹,这篇文章来讲 TypeScript 中的数组以及元组类型


1、概述

在 JavaScript 中的数组,在 TypeScript 里,可具体分为数组以及元组两种类型

先来简单对比一下区别

  • JavaScript 中的数组,可以用于存放类型不同数量不定的元素
  • TypeScript 中的数组,通常用于存放类型相同数量不定的元素
  • TypeScript 中的元组,通常用于存放类型不同数量确定的元素

下面我们再来详细介绍  TypeScript  中数组以及元组的相关概念和使用


2、数组

(1)特征

TypeScript 中的数组本质上也是特殊的对象,这与 JavaScript 中的数组是一样的

除此之外,TypeScript 中的数组有两个关键的特征

  1. 元素类型必须相同【重要】
  2. 元素数量可以改变【重要】

这些特征决定着 TypeScript 数组的使用场景


(2)声明

显式声明数组类型写法有二:

  1. 类型后加方括号 []
  2. 泛型,这里只介绍写法,关于泛型的详细说明,后面会写一篇文章单独介绍
// 写法一:类型后加方括号
// 情况一:普通类型
let arr1:number[] = [1, 2, 3];

// 写法一:类型后加方括号
// 情况二:复杂类型
let arr2:(number|string)[] = [1, '2', 3];

// 写法一:类型后加方括号
// 情况三:多维数组
let arr3:(number|string)[][] = [[1], ['2', 3]];

// 写法二:泛型
// 情况一:普通类型
let arr4:Array<number> = [1, 2, 3];

// 写法二:泛型
// 情况二:复杂类型
let arr5:Array<number|string> = [1, '2', 3];

// 写法三:泛型
// 情况三:多维数组
let arr6:Array<Array<number|string>> = [[1], ['2', 3]];

// 验证二:元素数量可以改变

arr1.pop();      // 编译正常
arr1.push(4);    // 编译正常

// 验证一:元素类型必须相同

arr1.push('56'); // 编译报错
arr1.push(true); // 编译报错

如果没有显式声明就会进行隐式推导

let arr0 = [];              // 此时推断为:never[]
let arr1 = [1, 2, 3];       // 此时推断为:number[]
let arr2 = [1, '2', 3];     // 此时推断为:(number|string)[]
let arr3 = [[1], ['2', 3]]; // 此时推断为:(number|string)[][]

arr1.push('1'); // 编译报错
arr0.push('1'); // 只是语法提示错误,但是编译没有报错?

(3)只读数组

如果想要声明一个只读数组,你可能会第一时间想到使用  const  关键字

但是就和对象一样,使用  const  只是不能重新赋值,但仍可以修改元素,不是真正意义上的只读

const arr0:number[] = [1, 2, 3];

// 删、增、改,都可执行
// 但重新赋值,不被允许

arr0.pop();   // 编译正常
arr0.push(4); // 编译正常
arr0[0] = 0;  // 编译正常

arr0 = [1, 3, 5]; // 编译报错

想要实现真正意义上的只读,可以使用 readonly 关键字或特殊泛型 Readonly / ReadonlyArray

// 声明只读数组:使用 readonly 关键字,注意 readonly 关键字不能和泛型一起使用

const arr1:readonly number[] = [1, 2, 3];

arr1.pop();   // 编译报错
arr1.push(4); // 编译报错
arr1[0] = 0;  // 编译报错

// 声明只读数组:使用 Readonly 或 ReadonlyArray 泛型

const arr2:Readonly<number[]> = [1, 2, 3];
const arr3:Readonly<Array<number>> = [1, 2, 3];

const arr4:ReadonlyArray<number> = [1, 2, 3];

实际上,只读数组没有 poppush 等修改元素的方法,因此可进行的操作是普通数组操作的子集

【引理】由于子类型会在继承父类型的基础上增加自己的方法

【推论】也就是说,普通数组是只读数组的子类型,只读数组是普通数组的父类型

【引理】由于任何能使用父类型的地方都可以使用子类型代替,但反之不行

【推论】所以只读数组无法代替普通数组直接使用

想要解决这一问题,可以用类型断言,告诉编译器某个值确实就是某种类型

function sumArr(arr: number[]) {
  return arr.reduce((prev, curr) => prev + curr);
}

// 普通数组
const arr1:number[] = [1, 2, 3];
sumArr(arr1); // 编译正常

// 只读数组
const arr2:readonly number[] = [1, 2, 3];
sumArr(arr2); // 编译报错
sumArr(arr2 as number[]); // 类型断言,编译正常

3、元组

(1)特征

元组本质上就是特殊的数组,可以看作是数组的子类型

元组关键的特征也是有两个,正好与数组相反:

  • 元素类型可以不同,但在声明时需要指定【重要】
  • 元素数量无法改变,也在声明时就要指定【重要】

这些不同的特征决定着二者有不同的应用场景


(2)声明

元组类型必须显式声明,因为元组的赋值语法和数组赋值一模一样

并且就像上面说的那样,声明元组时需要单独为每个元素指定类型

// 显式声明时:
// 若是成员的类型写在方括号之外,则说明是数组类型
// 若是成员的类型写在方括号之内,则说明是元组类型【重要,注意区别】

// 声明元组
// 单一类型,数量为三
let tuple1:[number, number, number] = [1, 2, 3];   // 赋值的语法,数组和元组一样

// 声明元组
// 不同类型,数量为三
let tuple2:[number, string, number] = [1, '2', 3]; // 赋值的语法,数组和元组一样

如果没有进行显式声明,那么就会被默认推导为数组

let arr1 = [1, 2, 3]; // 这个语句在介绍数组时已经出现过

(3)可选元素

就和对象类型一样,元组类型同样支持可选元素,这样赋值时该元素就可以被忽略不算

只需声明元组类型时在该元素后面加上问号即可,但要注意可选元素要在必选元素之后

// 声明时
let tuple1:[number?, string]; // 编译报错,可选元素要在必选元素之后
let tuple2:[number, string?]; // 编译正常

// 赋值时
tuple2 = [1, '2']; // 可选元素既能赋值
tuple2 = [1];      // 也能省略

元组中的元素数量通常在声明时就能确定具体值,但是使用可选元素之后就没这么简单了

编译器在进行访问检查时会认为:所有可能的元素数量都是合法的

// 声明时
let tuple2:[number, string?] = [1];

// 访问时
tuple2[0]; // 编译正常(元素数量有可能是 1,当可选元素被赋值时),运行时访问结果也符合预期,为 1
tuple2[1]; // 编译正常(元素数量有可能是 2,当可选元素被忽略时),运行时访问结果不符合预期,为 undefined
tuple2[2]; // 编译报错(元素数量不可能是 3)

(4)剩余元素

元组类型支持剩余元素,表示可以接收任意多个指定类型的元素

声明时可以在任意位置,使用拓展运算符来展开一个数组或元组

// 剩余元素放在最后,拓展运算符 (...) 后面是布尔类型数组
let tuple1:[number, string, ...boolean[]];

// 剩余元素放在中间,拓展运算符 (...) 后面是布尔类型数组
let tuple2:[number, ...boolean[], string];

// 剩余元素放在最前,拓展运算符 (...) 后面是布尔类型数组
let tuple3:[...boolean[], number, string];

// 赋值
tuple1 = [1, '2'];              // 剩余元素赋予零个值也行
tuple1 = [1, '2', true];        // 剩余元素赋予一个值也行
tuple1 = [1, '2', true, false]; // 剩余元素赋予两个值也行

// 使用场景,如:
// 指定一个至少接收一个字符串的元组
let tuple4:[string, ...string[]];

(5)只读元组

最后再来介绍只读元组,就和数组一样,const 关键字无法做到真正意义上的只读

正确的做法是使用 readonly 关键字或特殊泛型 Readonly

// 声明只读元组:使用 readonly 关键字
let tuple1:readonly [number, string];

// 声明只读元组:使用 Readonly 泛型
let tuple2:Readonly<[number, string]>;


好啦,本文到此结束,感谢您的阅读!

如果你觉得这篇文章有需要修改完善的地方,欢迎在评论区留下你宝贵的意见或者建议

如果你觉得这篇文章还不错的话,欢迎点赞、收藏、关注,你的支持是对我最大的鼓励 (/ω\)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值