泛型 - 8
泛型
- 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
- 泛型
T
作用域只限于函数内部使用
什么叫泛型?在定义一个值的类型的时候,先不指定具体的类型,在执行的时候,才确定具体的类型。
什么时候用泛型?定义函数、类
1 泛型函数
为什么会有泛型,它的意义在哪里???看下面这个例子
首先,我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值
namespace a {
function createArray(length: number, value: any): Array<any> {
let result: Array<any> = [];
for (let i = 0; i < length; i++) {
result[i] = 1
}
return result
}
let result = createArray(3, "x");
console.log(result);
}
在上面这个例子中,value 无法确定类型,导致返回的数组中的类型不能确定,我传入的是个字符串的数组,返回的是数字的数组。
此时可以使用泛型,让其接受了什么类型,就返回指定的类型。
namespace b {
function createArray<T>(length: number, value: T): Array<T> {
let result: Array<T> = [];
for (let i = 0; i < length; i++) {
// result[i] = 1 // <string> 传入 <T>, 表示一定要是 string 类型的,这里赋值一个数字类型的就会报错
result[i] = value;
}
return result
}
let result = createArray<string>(3, "x");
console.log(result);
}
泛型存在 作用域 的概念,它在确定类型之后,只在函数内部被确定使用。也就是我们是可以在外部多次定义T
的类型。
namespace c {
function createArray<T>(length: number, value: T): Array<T> {
let result: Array<T> = [];
for (let i = 0; i < length; i++) {
// result[i] = 1 // <string> 传入 <T>, 表示一定要是 string 类型的,这里赋值一个数字类型的就会报错
result[i] = value;
}
return result
}
let result = createArray<string>(3, "x");
console.log(result); // ["x", "x", "x"]
let result2 = createArray<number>(3, 3); // <number>就相当于一个参数,传递给<T>
console.log(result2); // [3, 3, 3]
}
2 类数组 ArrayLike (比如:arguments)
function sum(...args2: any[]) {
let args: IArguments = arguments;
for (let i = 0; i < args.length; i++) {
console.log(args[i]);
}
}
sum(1, 2, '3');
let root: HTMLElement | null = document.getElementById("root");
let children: HTMLCollection = root!.children
let childNodes: NodeListOf
3 类的泛型
class MayArray<T>{
private list: T[] = [];
add(val: T) {
this.list.push(val)
}
getMax(): T {
let result: T = this.list[0];
for (let i = 0; i < this.list.length; i++) {
if (this.list[i] > result) {
result = this.list[i]
}
}
return result;
}
}
let arr = new MayArray<number>();
arr.add(1);
arr.add(2);
arr.add(3);
let result = arr.getMax();
console.log(result);
4 接口泛型
namespace c {
interface Calculate {
<T>(a: T, b: T): T
}
let add: Calculate = function <T>(a: T, b: T) {
return a + b; // 报错,泛型不能进行相加,因为泛型 T 的是无法确定类型的。
}
let result = add<number>(1, 2)
console.log(result);
}
5 多个类型参数
我们通过下面这道题来展示多个类型参数是如何使用的。
题目是:如何在不增加中间变量的情况下,交换两个变量的值
function swap<A, B>(tuple: [A, B]): [B, A] {
return [tuple[1], tuple[0]]
}
let result1 = swap<string, number>(["xx", 12])
console.log(result1);
6 默认泛型类型
和函数形参可以接受默认值是一样,用<T = number>
来表述默认类型
function createArray<T = number>(): T | null {
let t: T | null = null;
return t;
}
let result2 = createArray();// 不穿类型给 T,T 是默认类型 number, 所以 result2 是 number 类型
let result3 = createArray<string>(); // 传了 string,上面的 T 就是string,所以 result2 是 string 类型
7 泛型的约束
在函数中使用泛型的时候,由于预先并不知道具体的类型,所以不能访问相应的类型的方法
function logger<T>(val: T) {
console.log(val.length); //报错:val 的类型不确定,所以不能直接反问 val.length(val可能是数字类型,就不会有length属性)
}
// 解决办法:
// 1. 泛型 继承一个 接口
interface LengthWise {
length: number
}
function logger2<T extends LengthWise>(val: T) {
console.log(val.length);
}
logger2("ruhua")
// 2. 泛型 继承一个 类
function logger3<T extends string>(val: T) {
console.log(val.length);
}
logger3("ruhua")
8 泛型 和 接口的混合使用练习
interface Cart<T> {
list: T[]
}
let cart: Cart<string> = {
list: ["1", "2", "3"]
}
9 泛型类型别名
- 泛型类型别名可以表达更复杂的类型
type Cart2<T> = { list: T[] } | T[];
let c1: Cart2<string> = { list: ["1"] }
let c2: Cart2<string> = ["1"]
10 泛型接口 VS 泛型类型别名
- 接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名
- 类型别名不能被 extends(继承) 和 implements(实现),这时我们应该尽量使用接口代替类型别名
- 当我们需要使用联合类型或者元组类型的时候,类型别名会更合适
interface 定义一个实实在在的接口,它是一个真正的类型。 type 一般用来定义别名,并不是一个真正的类型
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界