泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型,类型参数是一种特殊的变量,它代表类型而不是值。
泛型函数的基本语法如下:
function 函数名<类型参数>(参数: 类型参数): 类型参数 {
// 函数体
return 参数;
}
一 泛型类和接口
类和接口可以定义为泛型,将参数添加到类型定义中,如以下示例中的类型参数Element:
class CustomStack<Element> {
public push(e: Element):void {
// ...
}
}
要使用类型CustomStack,必须为每个类型参数指定类型实参:
let s = new CustomStack<string>();
s.push('hello');
编译器在使用泛型类型和函数时会确保类型安全。参见以下示例:
let s = new CustomStack<string>();
s.push(55); // 将会产生编译时错误
二 泛型约束
泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的Key类型参数必须具有hash方法。
interface Hashable {
hash(): number;
}
class MyHashMap<Key extends Hashable, Value> {
public set(k: Key, v: Value) {
let h = k.hash();
// ...其他代码...
}
}
在上面的例子中,Key类型扩展了Hashable,Hashable接口的所有方法都可以为key调用。
三、泛型函数
使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:
function last(x: number[]): number {
return x[x.length - 1];
}
last([1, 2, 3]); // 3
如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:
function last<T>(x: T[]): T {
return x[x.length - 1];
}
现在,该函数可以与任何数组一起使用。
在函数调用中,类型实参可以显式或隐式设置:
// 显式设置的类型实参
last<string>(['aa', 'bb']);
last<number>([1, 2, 3]);
// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
last([1, 2, 3]);
四 泛型默认值
泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。下面的示例展示了类和函数的这一点。
class SomeType {}
interface Interface <T1 = SomeType> { }
class Base <T2 = SomeType> { }
class Derived1 extends Base implements Interface { }
// Derived1在语义上等价于Derived2
class Derived2 extends Base<SomeType> implements Interface<SomeType> { }
function foo<T = number>(): T {
// ...
}
foo();
// 此函数在语义上等价于下面的调用
foo<number>();
五 泛型的应用场景
- 创建通用的数据结构:如通用的栈、队列等数据结构,使用泛型可以让这些数据结构适用于不同类型的数据。
- 封装通用的算法:例如排序算法,使用泛型可以让算法适用于不同类型的数据。
通过使用泛型类型和函数,你可以编写更加灵活、可复用和类型安全的代码,避免代码的重复编写,提高开发效率。