泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。在定义函数、接口或类时遇到类型不明确就可以使用泛型。
泛型名是任意的,可以是T也可以是K和U等,泛型可以同时指定多个。泛型就相当于指定一个变量名来接受类型。
泛型的基本使用
举个例子,需求是定义一个函数,需要传入两个参数,第一个参数是数据,第二个参数是数量。函数的作用是根据数量产生对应个数的数据,存放在一个数组中。不知道数据的类型可以使用泛型解决。使用泛型在定义时不需要先确定类型,而是使用时来确定类型,如果没有确定会根据类型推断确定类型。
// T是泛型,表示任意输入的类型
const getArr = <T>(value:T,count:number):T[] => {
const arr:T[] = [];
for(let i = 0;i<count;i++){
arr.push(value);
};
return arr
};
getArr("267",5);
getArr<number>(678,2);
在函数名后添加了 <T>,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array<T> 中即可使用了。
接着在调用的时候,可以指定它具体的类型为 string。当然,也可以不手动指定,而让类型推论自动推算出来。
声明式函数泛型语法
// 泛型名是任意的,可以是T也可以是K等
function fn<T>(a:T):T{
return a;
}
// 不指定泛型根据类型推断确定类型
fn(10);// 根据推断原则即本次调用的泛型T就是number
// 指定泛型,泛型的类型就是指定的类型
fn<string>('golden star');//指定的泛型就是string类型
定义式泛型语法
//匿名函数
let a = function <T>(arg: T): T {
return arg;
}
//箭头函数
let a = <T>(arg: T):T =>{
return arg;
}
多个泛型参数的函数
定义泛型的时候,可以一次定义多个类型参数:
// 泛型可以指定多个
function fm<T,K>(a:T,b:K):T{
console.log(b);
console.log(typeof b);
return a;
}
let result=fm<string,number>('6ara 4ever',666666);
console.log(typeof result);
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
swap(["six",6]);
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法。
function loggingIdentity<T>(arg: T): T {
console.log(arg);
//获取参数的长度
return arg.length;
}
// 编译报错: Property 'length' does not exist on type 'T'.
泛型 T 不一定包含属性 length,所以编译的时候报错了。这时,我们可以使用泛型进行约束。只允许这个函数传入那些包含 length 属性的变量,这就是泛型约束。
interface internum{
length:number;
}
function fg<U extends internum>(arg:U):number{
console.log(arg);
return arg.length;
}
const res1=fg({name:'golden star',length:6});
console.log(res1);
const res2=fg('6ara 4ever');
console.log(res2);
U extends internum表示泛型U必须实现接口internum。使用了 extends 约束了泛型 U 必须符合接口 internum 的形状,也就是必须包含 length 属性。
此时如果调用 fg 的时候,传入的 arg 不包含 length,那么在编译阶段就会报错了。
interface internum{
length:number;
}
function fg<U extends internum>(arg:U):number{
console.log(arg);
return arg.length;
}
const res1=fg({name:'golden star'});
多个类型参数之间也可以互相约束:
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
// 编译报错
copyFields(x, { b: 10, d: 20,e:9 });
泛型接口
使用接口的方式来定义一个函数需要符合的形状:
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
也可以使用含有泛型的接口来定义函数的形状:
// 定义一个泛型接口
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
进一步,我们可以把泛型参数提前到接口名上:
interface CreateArrayFunc<T> {
(length: number, value: T): T[];
}
let createArray: CreateArrayFunc<number>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
注意,此时在使用泛型接口的时候,在使用泛型时需要定义泛型的类型。
泛型类
与泛型接口类似,泛型也可以用于类的类型定义中
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x:number, y:number) { return x + y; };
myGenericNumber.sex = '男'; //编译报错,因为类中没有声明sex
new创建的新对象是空对象{ },其中无成员。对象中只能全部或部分实现类中声明好的成员,不能增加其它成员 。
class GenericNumber<T> {
name: T;
constructor(arg:T){
this.name=arg;
}
}
let myGenericNumber = new GenericNumber<string>('revelation');
泛型参数的默认类型
可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}