在 TypeScript 中咱们会使用泛型来对函数的相关类型进行约束。这里的函数,同时包含 class 的构造函数,所以,一个类的声明部分,也可使用泛型。那么,究竟什么是泛型?若是通俗的理解泛型呢?php
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。html
通俗的解释,泛型是类型系统中的“参数”,主要做用是为了类型的重用。从上面定义能够看出,它只会用在函数、接口和类中。它和 js 程序中的函数参数是两个层面的事物(虽然意义是相同的),由于 typescript 是静态类型系统,是在 js 进行编译时进行类型检查的系统,所以,泛型这种参数,其实是在编译过程当中的运行时使用。之因此称它为“参数”,是由于它具有和函数参数如出一辙的特性。git
function increse(param) {
// ...
}复制代码
而类型系统中,咱们如此使用泛型:github
function increase<T>(param: T): T {
//...
}复制代码
当 param 为一个类型时,T 被赋值为这个类型,在返回值中,T 即为该类型从而进行类型检查。typescript
要知道 typescript 自己的类型系统也须要编程,只不过它的编程方式很奇怪,你须要在它的程序代码中穿插 js 代码(在 ts 代码中穿插 js 代码这个说法很怪,由于咱们直观的感受是在 js 代码中夹杂了 ts 代码)。编程
编程中,最重要的一种形式就是函数。在 typescript 的类型编程中,你看到函数了吗?没有。这是由于,有泛型的地方就有函数,只是函数的形式被 js 代码给割裂了。typescript 须要进行编译后获得最终产物。编译过程当中要作两件事,一是在内存中运行类型编程的代码,从而造成类型检查体系,也就是说,咱们可以对 js 代码进行类型检查,首先是 typescript 编译器运行 ts 编程代码后获得了一个运行时的检查系统本文来自否子戈的播客,运行这个系统,从而对穿插在其中的 js 代码进行类型断言;二是输出 js,输出过程当中,编译系统已经运行完了类型编程的代码,就像 php 代码中 echo js 代码同样,php 代码已经运行了,显示出来的是 js 代码。bash
从这个角度看 typescript,你或许更能理解为何说它是 JavaScript 的超集,为何它的编译结果是 js(为何不能够将 ts 编译为其余语言呢?)。闭包
既然咱们理解了 ts 编译系统的逻辑,那么咱们就能够把类型的编程和 js 自己的业务编程在情感上区分开。咱们所讲的“泛型”,只存在于类型编程的部分,这部分代码是 ts 的编译运行时代码。ide
咱们来看下一个简单的例子:函数
function increase<T>(param: T): T {
//...
}复制代码
这段代码,若是咱们把 js 代码区分开,而后用类型描述文原本表示会是怎样?
// 声明函数 @type,参数为 T,返回结果为 (T): T
@type = T => (T): T
// 运行函数获得一个类型 F,即类型为 (number): number
@F = @type(number)
// 要求 increase 这个函数符合 F 这种类型,也就是参数为 number,返回值也为 number
@@F
function increase(param) {
// ...
}
@@end复制代码
实际上没有 @@F 这种语法,是我编造出来的,目的是让你能够从另外一个角度去看类型系统。
当咱们理解泛型是一种“参数”以后,咱们可能会问:类型系统的函数在哪里?对于 js 函数而言,你能够很容易指出函数声明语句和参数,可是 ts 中,这个部分是隐藏起来的。不过,咱们能够在一些特定结构中,比较容易看到类型函数的影子:
// 声明一个泛型接口,这个写法,像极了声明一个函数,咱们用描述语言来形容 @type = T => (T): T
interface GenericIdentityFn<T> {
(arg: T): T;
}
// 这个写法,有点像一个闭包函数,在声明函数后,当即运行这个函数,描述语言:@@[T => (T): T](any)
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型接口,像极了调用一个函数,咱们用描述语言来形容 @type(number)
let myIdentity: GenericIdentityFn<number> = identity;复制代码
上面这一整段代码,咱们用描述文本重写一遍:
@GenericIdentityFn = T => (T): T
@@[T => (T): T](any)
function identify(arg) {
return arg
}
@@end
@@GenericIdentityFn(number)
let myIdentity = identity
@@end复制代码
咱们在类型系统中声明了两个函数,分别是 @GenericIdentityFn 和 @some(匿名函数 @[T => (T): T])。虽然是两个函数,可是实际上,它们的是如出一辙的,由于 typescript 是结构类型,也就是在类型检查的时候只判断结构上的每一个节点类型是否相同,而不是必须保持类型变量自己的指针相同。@GenericIdentityFn 和 @some 这两个函数分别被调用,用来修饰 identify 和 myIdentify,在调用的时候,接收的参数不一样,因此致使最终的类型检查规则是不一样的,identify 只要保证参数和返回值的类型相同,至于具体什么类型,any。而 myIdentify 除了保证参数返回值类型相同外,还要求类型必须是 number。
除了泛型接口,class 类也能够泛型化,即“泛型类”,借助泛型类,咱们来探究一下泛型的声明和使用的步骤。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();复制代码
前文泛型接口由于只是为了约束函数的类型,因此写的很像函数,实际上,咱们能够用描述语言从新描述一个泛型接口和泛型类。上面的红色部分,咱们用描述语言来描述:
@GenericNumber = T => class {
zeroValue: T;
add: (x: T, y: T) => T;
}复制代码
@GenericNumber 这个函数,以 T 为参数,返回一个 class,在 @type 函数体内屡次用到了参数 T。
@GenericIdentityFn = T => interface {
(arg: T): T;
}复制代码
咱们从新描述了前面的 interface GenericIdentityFn,这样咱们就能够在接口中增长其余的方法。
接下来咱们要再描述一个复杂的类型:
class Animal {
numLegs: number;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}复制代码
咱们姑且不去看 new() 的部分,咱们看尖括号中的 extends 语法,这里应该怎么理解呢?实际上,咱们面对的问题是,在编译时,<A extends Animal> 尖括号中的内容是何时运行的,是以前,仍是之间?
// 究竟是
@type = (A extends Animal) => (new() => A): A
@type(T)
// 仍是
@type = A => (new() => A): A
@type(T extends Animal)复制代码
由于 typescript 是静态类型系统,Animal 是不变的类,所以,能够推测其实在类的建立以前,尖括号的内容已经被运行了。
@type = (A extends Animal) => (new() => A): A复制代码
也就是说,要使用 @type(T) 产生类型,首先 T 要知足 Animal 的结构,而后才能获得须要的类型,若是 T 已经不知足 Animal 类的结构了,那么编译器会直接报错,而这个报错,不是类型检查阶段,而是在类型系统的建立阶段,也就是 ts 代码的运行阶段。这种状况被称为“泛型约束”。
另外,相似 <A,B> 这样的语法其实和函数参数一致。
@type = (A, B) => (A|B): SomeType复制代码
咱们再来看 ts 内置的基础类型:Array<number>
@Array = any => any[]复制代码
Typescript 中的泛型,实际上就是类型的生成函数的参数。本文的内容所有为凭空想象,仅适用于对 ts 进行理解时的思路开拓,不适用于真实编程,特此声明。
本文发布于个人博客:www.tangshuang.net/7473.html
欢迎来个人播客与我交流,也但愿你经过下方二维码给我打赏。