返回

TS中的泛型是啥玩意?详解一下!

前端

前言

关于TS我们介绍已经有许多了,这节我们来看泛型。关于之前其他内容的介绍可以自行移步:

  • 一分钟看完,TS中的类的继承以及多态
  • 一分钟看完,TS中的类、接口和类型别名
  • 一分钟看完,TS中的修饰符
  • 一分钟看完,TS中的联合类型和元组类型
  • 一分钟看完,TS中的映射类型和交叉类型
  • 一分钟看完,TS中的类以及它的成员

正文

一、什么是泛型

泛型是一种在编写代码时使用参数化类型的方法,以便能够在一个地方定义代码,而无需为每种可能的数据类型都编写单独的代码。泛型允许我们创建可重用的代码组件,这些组件可以与任何类型的数据一起使用。

二、泛型函数

泛型函数是使用泛型类型参数定义的函数。泛型函数可以接受任何类型的数据作为参数,并返回任何类型的数据。

例如,我们可以定义一个泛型函数swap来交换两个变量的值:

function swap<T>(a: T, b: T): void {
  let temp = a;
  a = b;
  b = temp;
}

这个函数使用了一个泛型类型参数T,表示要交换的变量的类型。我们可以用这个函数来交换任何类型的数据,例如:

swap(1, 2); // 交换两个数字
swap("a", "b"); // 交换两个字符串
swap([1, 2, 3], [4, 5, 6]); // 交换两个数组

三、泛型类

泛型类是使用泛型类型参数定义的类。泛型类可以创建具有特定类型参数的对象。

例如,我们可以定义一个泛型类Stack来表示栈:

class Stack<T> {
  private data: T[] = [];

  push(item: T) {
    this.data.push(item);
  }

  pop(): T | undefined {
    return this.data.pop();
  }

  peek(): T | undefined {
    return this.data[this.data.length - 1];
  }

  isEmpty(): boolean {
    return this.data.length === 0;
  }
}

这个类使用了一个泛型类型参数T,表示栈中元素的类型。我们可以用这个类来创建具有任何类型元素的栈,例如:

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 3
console.log(numberStack.peek()); // 2

const stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
stringStack.push("c");
console.log(stringStack.pop()); // c
console.log(stringStack.peek()); // b

四、泛型接口

泛型接口是使用泛型类型参数定义的接口。泛型接口可以定义具有特定类型参数的方法和属性。

例如,我们可以定义一个泛型接口Comparator来表示比较函数:

interface Comparator<T> {
  (a: T, b: T): number;
}

这个接口使用了一个泛型类型参数T,表示要比较的元素的类型。我们可以用这个接口来定义比较函数,例如:

const numberComparator: Comparator<number> = (a, b) => a - b;
const stringComparator: Comparator<string> = (a, b) => a.localeCompare(b);

这些比较函数可以用于对任何类型的数据进行排序,例如:

const numbers = [1, 2, 3, 4, 5];
numbers.sort(numberComparator);
console.log(numbers); // [1, 2, 3, 4, 5]

const strings = ["a", "b", "c", "d", "e"];
strings.sort(stringComparator);
console.log(strings); // ["a", "b", "c", "d", "e"]

五、泛型约束

泛型约束允许我们在定义泛型类型参数时指定一些限制条件。这可以确保泛型类型参数只能是满足这些限制条件的类型。

例如,我们可以定义一个泛型类Queue来表示队列,并使用泛型约束来确保队列中的元素只能是实现了Comparable接口的类型:

interface Comparable<T> {
  compareTo(other: T): number;
}

class Queue<T extends Comparable<T>> {
  private data: T[] = [];

  enqueue(item: T) {
    this.data.push(item);
  }

  dequeue(): T | undefined {
    return this.data.shift();
  }

  peek(): T | undefined {
    return this.data[0];
  }

  isEmpty(): boolean {
    return this.data.length === 0;
  }
}

这个类使用了一个泛型类型参数T,表示队列中元素的类型。我们还使用了一个泛型约束T extends Comparable<T>来确保T类型必须实现了Comparable接口。这意味着我们可以用这个类来创建具有任何类型元素的队列,只要这些元素实现了Comparable接口,例如:

class Number implements Comparable<Number> {
  private value: number;

  constructor(value: number) {
    this.value = value;
  }

  compareTo(other: Number): number {
    return this.value - other.value;
  }
}

const numberQueue = new Queue<Number>();
numberQueue.enqueue(new Number(1));
numberQueue.enqueue(new Number(2));
numberQueue.enqueue(new Number(3));
console.log(numberQueue.dequeue()); // Number { value: 1 }
console.log(numberQueue.peek()); // Number { value: 2 }

class String implements Comparable<String> {
  private value: string;

  constructor(value: string) {
    this.value = value;
  }

  compareTo(other: String): number {
    return this.value.localeCompare(other.value);
  }
}

const stringQueue = new Queue<String>();
stringQueue.enqueue(new String("a"));
stringQueue.enqueue(new String("b"));
stringQueue.enqueue(new String("c"));
console.log(stringQueue.dequeue()); // String { value: "a" }
console.log(stringQueue.peek()); // String { value: "b" }

六、泛型工厂函数

泛型工厂函数是使用泛型类型参数定义的函数,该函数返回一个具有特定类型参数的实例。

例如,我们可以定义一个泛型工厂函数createStack来创建一个具有特定类型参数的栈:

function createStack<T>(): Stack<T> {
  return new Stack<T>();
}

这个函数使用了一个泛型类型参数T,表示栈中元素的类型。我们可以用这个函数来创建具有任何类型元素的栈,例如:

const numberStack = createStack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 3
console.log(numberStack.peek()); // 2

const stringStack = createStack<string>();
stringStack.push("a");
stringStack.push("b");
stringStack.push("c");
console.log(stringStack.pop()); // c
console.log(stringStack.peek()); // b

七、泛型方法

泛型方法是使用泛型类型参数定义的类或接口的方法。泛型方法可以接受任何类型的数据作为参数,并返回任何类型的数据。

例如,我们可以定义一个泛型类List来表示列表,并使用泛型方法map来将列表中的每个元素映射到一个新的元素:

class List<T> {
  private data: T[] = [];

  map<U>(f: (item: T) => U): List<U> {
    const result = new List<U>();
    for (const item of this.data