软件工程的一个主要部分就是构建组件,构建的组件不仅需要具有明确的定义和统一的接口,同时也需要组件可复用。支持现有的数据类型和将来添加的数据类型的组件为大型软件系统的开发过程提供很好的灵活性。

在C#和Java中,可以使用"泛型"来创建可复用的组件,并且组件可支持多种数据类型。这样便可以让用户根据自己的数据类型来使用组件。

泛型的简单案例

首先,用泛型写一个"Hello World":identity函数。identity函数将会返回我们传入的数据。你可以认为它是个"echo"命令。
不用泛型,我们也不用给identity函数指定类型:

function identity(arg: number): number {
return arg;
}

或者,我们可以给identity函数指定"any"类型:

function identity(arg: any): any {
return arg;
}

虽然使用"any"类型的时候可以接收任何类型的"arg"参数,但是实际上已经失去函数返回值类型的信息。假如我们传入一个number,我们只知道返回任何类型的值都是可以的。

所以,我们需要一直方式来捕捉参数的类型,也可以用它来表示返回值的类型。这里使用的是"类型变量",一种特殊的变量,代表的是类型而非值。

function identity<T>(arg: T): T {
return arg;
}

现在我们已经为identity函数添加了类型变量"T"。"T"允许捕获用户提供的参数类型(如:number),以便我们稍后可以使用该类型。然后我们再次用"T"作为返回值的类型。现在我们可以看到,同一类型被用来作为参数类型和返回值类型。

我们称这个版本的identity函数为泛型,它可用于多种类型。与使用"any"类型不同,它和第一个identity函数(使用number作为参数类型和返回值类型)一样精准(它不会失去任何信息)。

一旦我们定义了泛型函数,有两种方法可以使用。第一种就是传入所有的参数,包括类型参数:

var output = identity<string>("myString"); // output的类型将会是 'string'

在这里,我们明确的将"T"指定为string,作为函数中传入的参数,使用<>包裹该参数而非()。

第二种是最常见的。我们使用/类型推断/,我们希望编译器根据传入的参数自动为"T"指定类型。

var output = identity("myString"); // output的类型将会是 'string'

注意,我们并未显示的给尖括号<>内传入类型,编译器检查"myString",然后将"T"设置为它的类型。虽然类型推断是个很实用的工具,也能够使代码简短易读,但你还是需要跟前面的例子一样明确的传递类型参数,因为可能会存在复杂的函数,使得编译器未能正确的进行类型推断。

使用泛型

当你开始使用泛型,你可能会注意到当你创建一个类似"identity"的泛型函数,编译器会强制要求你在函数中正确的使用这些通用类型参数。也就是说,你真的把这些参数视为可以是任何类型的。

再看看之前的identity函数:

function identity<T>(arg: T): T {
return arg;
}

想要每次调用的时候在控制台打印出"arg"参数的length。我们可以尝试这么写:

function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // 错误: T 不存在 .length
return arg;
}

当我们这么做的时候,编译器会抛出一个错误提示我们使用"arg"的".length"属性,但是没有地方指定过"arg"的".length"属性。之前我们说类型变量代表了所有类型,所以可能使用这个函数的时候会传入一个"number"类型的值,而"number"是没".length"属性的。
实际上,我们想让这个函数接受的参数是个"T"类型的数组而非直接"T"。当传入的是数组,length属性便是可用的了。我们可以像创建其他数组类型一样:

function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // 数组中存在 .length,所以没报错
return arg;
}

你可以这样理解loggingIdentity函数:loggingIdentity泛型函数,参数类型是"T",参数"arg"是个类型为"T"的数组,返回的也是个类型为"T"的数组。如果我们传入一个都是数字的数组,那么我们也会得到一个都是数字的数组,因为这时候"T"类型已经绑定为number了。这使得我们可以使用类型变量"T"作为我们使用的类型的一部分,而非全部类型,这也更具灵活性。

我们可以通过这种方式写个例子:

function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // 数组中存在 .length,所以没报错
return arg;
}

泛型类型

在前面例子中,我们创建了通用的identity函数,可以使用于不同的类型。现在,我们将探讨函数类型及如何创建泛型接口。

泛型函数的类型与非泛型函数一样,只是最前面放上一个类型参数,类似与声明函数:

function identity<T>(arg: T): T {
return arg;
} var myIdentity: <T>(arg: T)=>T = identity;

我们也可以给类型中的泛型类型参数指定不同的名称,只要类型变量的数量和其使用方式都能对应的上。

function identity<T>(arg: T): T {
return arg;
} var myIdentity: <U>(arg: U)=>U = identity;

我们也可以使用对象字面量的签名调用来写泛型类型:

function identity<T>(arg: T): T {
return arg;
} var myIdentity: {<T>(arg: T): T} = identity;

下面开始写第一个泛型接口。用上个例子中的对象字面量来写接口:

interface GenericIdentityFn<T> {
(arg: T): T;
} function identity<T>(arg: T): T {
return arg;
} var myIdentity: GenericIdentityFn<number> = identity;
var num = myIdentity(10); // 正确,因为类型是number
var str = myIdentity("10") // 错误,参数类型不是number

注意,我们的例子稍微有些改变。我们把非泛型函数签名作为泛型类型的一部分,而不是去描述泛型函数。当我们使用GenericIdentityFn,我们还需要指定对应的类型参数(这里是number),有效的锁定在底层签名调用时会用到的类型。理解"何时将类型参数直接放到签名调用"和"何时将它放到接口上"将有助于描述哪部分类型属于泛型。

除了泛型接口,我们还可以创建泛型类。请注意,不可能创建泛型枚举和模块。

泛型类

泛型类和泛型接口相似。泛型类在类名后面使用尖括号<>包含泛型类型参数列表。

class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
} var myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 1;
myGenericNumber.add = function(x, y) { return x + y; };
alert(myGenericNumber.add(myGenericNumber.zeroValue, 1)); //

这是对"GenericNumber"类想当直观的使用,你也可能注意到并未限制只能使用"number"类型。我们可以使用"string"抑或更复杂的对象。

var stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "Hello ";
stringNumeric.add = function(x, y) { return x + y; }; alert(stringNumeric.add(stringNumeric.zeroValue, "World")); // Hello World

和接口一样,将类型参数放在类之后来告诉我们类的所有属性都是同一个类型。

正如前面"类"那一节所描述的,一个类由两部分组成:静态部分和实例部分。泛型类仅属于实例部分,所以当我们使用类的时候,静态成员不能使用类的类型参数。

泛型的限制

如果你还记得之前的例子,有时候你想要写一个泛型函数来操作一组类型,并且你是知道这些类型具有什么功能。

在"loggingIdentity"例子中,我们希望能够访问"arg"的".length"属性,但是编译器不能确定每个类型都有".length"属性,所以它将报错提示我们不能这么做。

function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // 错误: T 不存在 .length
return arg;
}

相对于处理任何类型或者所有类型,我们更希望强制去要求函数去处理带有".length"属性的任何类型或者所有类型。只要该类型有这个成员(属性),我们便运行通过,也就是必须包含这个指定的成员(属性)。

既然需要这么做,我们就创建一个描述限制的接口。在这里,先创建一个只有单个属性".length"的接口,然后使用这个接口和"extends"关键字来指明限制:

interface hasLength {
length: number;
} function loggingIdentity<T extends hasLength>(arg: T): T {
console.log(arg.length); // 现在我们知道它含有.length属性,并且不报错
return arg;
}

因为这个泛型函数现在是有限制的,所以它不在支持任何类型或者所有类型:

loggingIdentity(3); // 错误,number不包含.length属性

因此,我们需要传入其类型具有所需属性的值:

loggingIdentity({length: 10, value: 3}); // 正确

在泛型中使用类类型
当在TypeScript中使用泛型创建工厂函数的时候,需要引用其构造函数的类类型。

class Greeter{
greeter:string = "Hello World";
}
function create<T>(c: {new(): T}): T {
return new c();
}
var newGreeter = create<Greeter>(Greeter);

TypeScript Generics(泛型)的更多相关文章

  1. TypeScript Generics All In one

    TypeScript Generics All In one TypeScript 泛型 代码逻辑复用 扩展性 设计模式 方法覆写, 直接覆盖 方法重载,参数个数或参数类型不同 test " ...

  2. TypeScript Generics

    TypeScript Generics https://www.typescriptlang.org/docs/handbook/generics.html 泛型 1 Generic Interfac ...

  3. TypeScript 素描 - 泛型、枚举

    /* 泛型,好处多多的功能.不过这里最基本的就不打算说了,仅准备说一些 和C#不同的地方 */ /* 泛型接口 GenericIdentityFn 定义了方法的描述等 identity方法则是它的实现 ...

  4. [易学易懂系列|rustlang语言|零基础|快速入门|(13)|Generics泛型]

    [易学易懂系列|rustlang语言|零基础|快速入门|(13)] 有意思的基础知识 Generics泛型 我们今天来看看泛型. 什么是泛型? 我们来看看这样的情景: 我们要写一个函数,这个函数可以处 ...

  5. TypeScript 之 泛型

    https://m.runoob.com/manual/gitbook/TypeScript/_book/doc/handbook/Generics.html 泛型:可以支持多种类型的数据 泛型函数的 ...

  6. TypeScript入门-泛型

    泛型 要创建一个可重用的组件,其中的数据类型就必须要兼容很多的类型,那么如何兼容呢,TypeScript提供了一个很好的方法:泛型 Hello World 要兼容多种数据格式,可能会有人想到any,即 ...

  7. 8.Generics 泛型(Dart中文文档)

    这篇翻译的不好 如果你看API文档中的数组篇,你会发现类型一般写成List.<...>的写法表示通用类型的数组(未明确指定数组中的数据类型).通常情况泛型类型用E,T,S,K,V表示. W ...

  8. [Typescript] Generics using TypeScript

    In this lesson we cover the key reason why programming languages need generics. We then show how use ...

  9. React + TypeScript 实现泛型组件

    泛型类型 TypeScript 中,类型(interface, type)是可以声明成泛型的,这很常见. interface Props<T> { content: T; } 这表明 Pr ...

随机推荐

  1. Linux内核的文件预读readahead

    Linux的文件预读readahead,指Linux系统内核将指定文件的某区域预读进页缓存起来,便于接下来对该区域进行读取时,不会因缺页(page fault)而阻塞.因为从内存读取比从磁盘读取要快很 ...

  2. WPF 显示文件列表中使用 ListBox 变到ListView 最后使用DataGrid

    WPF 显示文件列表中使用 ListBox 变到ListView 最后使用DataGrid 故事背景: 需要检索某目录下文件,并列出来,提供选择和其他功能. 第一版需求: 列出文件供选择即可,代码如下 ...

  3. composer "Illegal offset type in isset or empty"报错解决方案

    最近更新了composer版本,即执行以下任一命令 composer selfupdate | composer self-update 再次执行 composer update -vvv 会出现“I ...

  4. java的输入输出及相关快捷键

    首先:导入包import java.util.Scanner; 然后:在主函数中创建对象,eg:Scanner input=new Scanner(System.in);​ 最后,如果要输入字符串,则 ...

  5. Linux yum如何下载rpm包到本地

    下载前先安装一个小插件 [root@wang yum.repos.d]# yum install -y yum-plugin-downloadonly 安装一个包的同时 加上 yum install ...

  6. spring cron表达式

    其他参考资料 http://www.blogjava.net/hao446tian/archive/2012/02/13/369872.html http://blog.sina.com.cn/s/b ...

  7. 发现 OpenStack: 架构、功能和交互

    原文:http://www.ibm.com/developerworks/cn/cloud/library/cl-openstack-overview/index.html OpenStack 是由 ...

  8. 【转】How to hire——创业公司应该如何招人

    How to hire After startups raise money, their next biggest problem becomes hiring.  It turns out it’ ...

  9. 查看eclipse web项目中jsp编译后的servlet源文件【转】【JSP】

    eclipse中,jsp编译后 servlet源文件的位置为: F:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wor ...

  10. HDU 1257 最少拦截系统【LIS】

    题意:类似于套娃娃,问最少需要多少个拦截系统. 思路: 假设已经有m个导弹拦截序列 r1:x11>=x12>=x13>=...>=x1n r1:x21>=x22>= ...