定义:泛型容许咱们延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。编程
请看以下代码:数组
function identity<T>(arg: T) {
return arg;
}
function main() {
const result = identity<number>(1);
}
复制代码
咱们定义了一个泛型方法identity,定义了一个main方法,调用identity方法。bash
定义identity的时候,咱们并不知道参数arg是个什么类型,T就是一个占位符,告诉咱们arg是T类型的。编辑器
在调用identity的时候,才告诉它我要给你传递一个number类型的,而后传递真正的arg参数的值,是1。ide
这就是泛型定义中所说的,延迟编写方法(==identity==)中的编程元素(==arg参数==)的类型(==不写number,写T==),直到实际在程序中(==main方法==)使用它的时候。函数
上例中,将类型(number)变成了函数identity的一个参数,在调用的时候才传给函数identity,就叫作类型参数化。学习
看完上面的解释,你多半会疑惑,这么写有什么好处啊,之前那种不也挺好吗,以下:ui
function identity(arg: number) {
return arg;
}
function main() {
const result = identity(1);
}
复制代码
这不也实现了相同的逻辑吗?何须延迟到调用的时候,才肯定参数的类型呢?this
答案是 ==泛型可让不一样类型的数据,复用一样的方法或类。== 若是方法或类在定义的时候,就固定参数的类型,那么此方法或类只能处理这一种类型的数据。但等调用的时候才肯定参数类型,就可让多种参数类型的数据,复用同一个方法或类。spa
这里咱们分析一个稍微复杂的例子,内置对象Array的声明:
interface Array<T> {
length: number;
toString(): string;
pop(): T | undefined;
reverse(): T[];
sort(compareFn?: (a: T, b: T) => number): this;
...
}
复制代码
这里只是列出小部分Array的声明。我仍是按泛型的定义解释一边,在咱们编写Arry的时候,延迟定义编程元素(这里指在Array类内能够使用的T)的数据类型。
使用:
function main() {
const nums = new Array<number>(1, 2, 3);
nums.push(4);
const popNum = nums.pop();
}
复制代码
直到实际在main函数中使用它的时候。咱们才告诉Array,咱们要一个数字类型的数组。
咱们还能够
function main() {
const dates = new Array<Date>();
dates.push(new Date('2019-7-8'));
dates.push(new Date('2019-7-9'));
dates.push(new Date('2019-7-10'));
const reverseDates = dates.reverse();
}
复制代码
注意的问题的关键没,关键在于pop,reverse这样的方法的内在逻辑,是不关心被操纵数据的类型的,因此能够实现对于不一样类型的的复用。
因此,Array类就实现类对于不一样类型的数据列表的相关操做的复用。
若是您是由Javascript为基础,学习的TypeScript,或许会以为上面说的有些莫名其妙,TypeScript搞这个泛型干啥,JS对此彻底没有限制啊,没有类型,能够随意传参啊,以下:
function mainAboutNumber() {
var nums = new Array(1, 2, 3);
nums.push(4);
var popNum = nums.pop();
}
function mainAboutDate() {
var dates = new Array();
dates.push(new Date('2019-7-8'));
dates.push(new Date('2019-7-9'));
dates.push(new Date('2019-7-10'));
var reverseDates = dates.reverse();
}
复制代码
确实如此,若是没有类型,就没有泛型这一说了,泛型是对类型的一种放宽政策,但约束仍旧存在。若是是用JS,这样的代码在编写时不会出错:
function mainAboutDate() {
var dates = new Array();
dates.push(new Date('2019-7-8'));
dates.push('我不是Date类型');
dates.push(new Date('2019-7-10'));
var reverseDates = dates.reverse();
}
复制代码
但若是你的业务中,dates是表明一个日期列表,显然仍是TS更好:
会在咱们编程期间及时报错,泛型的约束力依旧存在,当给定参数的类型后,咱们就有了一个日期类型的数组了。
除了复用这条优势,泛型还能够帮助咱们实现入参和返回值类型一致的校验,以下:
function identity<T>(arg: T): T {
return arg;
}
复制代码
如方法后面的冒号+T,就是限定返回值的类型也是T,要和参数arg的T保持一致,当咱们使用它的时候:
将鼠标浮动到num变量上,发现它是个number类型,这是由于咱们将T设置成了numnber。经过类型推断,num就成了number类型,后面就能够经过编辑器的智能提示,看到number类型的全部方法了,方便咱们的编程。
在定义方法或类的时候,咱们不光能够使用T,还能够为T增长更多修饰,使其变成一种有更多信息的泛型变量。
以下是Object.freeze方法的签名。
Object.freeze() 方法能够冻结一个对象。一个被冻结的对象不再能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。 --- MDN定义
interface ObjectConstructor {
freeze<T>(a: T[]): ReadonlyArray<T>;
...
}
复制代码
咱们来看freeze(a: T[]): ReadonlyArray签名。 这个签名的意思是,若是你传递的是个数组(a:T[]),返回值会是一个ReadonlyArray。以下是使用时的代码:
function main() {
const nums = new Array<number>(1, 2, 3);
const freezeNums = Object.freeze(nums);
}
复制代码
咱们就获得了一个readonly的数字数组。这里的T[]和ReadonlyArray。利用T进一步描述freeze方法的参数和返回值。若是单单用T,显然信息量不够多。
另外假设让咱们来实现freeze方法。那内部咱们就能够调用a.length这样的代码,由于签名已经明确告诉咱们,入参a是某种数组类型。
泛型约束又是什么?咱们在看freeze方法的另外一个重载:
interface ObjectConstructor {
freeze<T>(a: T[]): ReadonlyArray<T>;
freeze<T extends Function>(f: T): T;
...
}
复制代码
不一样于泛型变量,这回咱们约束尖括号<>里的T,freeze(f: T): T;的意思是,若是传递给freeze的是个方法,那返回值仍是个方法。
再看一个例子:
interface SpecialType {
specialProperty: string;
}
function identity<T extends SpecialType>(arg: T): T {
console.log(arg.specialProperty);
return arg;
}
复制代码
这个例子中,咱们让T继承SpecialType,这样咱们在identity方法内部,就能够使用SpecialType规定的属性了。
泛型约束能够说增长了泛型的复用能力,可让某些含有相同属性的数据,复用一样须要这些属性的方法。
interface Array<T> {
...
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
...
}
复制代码
咱们来分析一下Array类中的map函数,也是咱们常常使用的函数。
第一个参数是个回调函数,回调函数的第一个参数value是T类型,第二个参数index是数字类型,第三个参数是T类型的列表。回调函数的返回值是一个U类型的数据。map函数的返回值是U类型的列表,是个全新类型的列表。
学习泛型,可让咱们很好的理解第三方库的TS声明。也可让咱们本身写出优秀的TypeScript代码。