泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
我们来创建一个identity函数。 这个函数会返回任何传入它的值。可以使用any来定义函数

  1. function identity(arg: any): any {
  2. return arg;
  3. }

使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
这个时候我们就可以使用泛型,我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型,这样参数类型与返回值类型是相同

  1. function identity<T>(arg: T): T {
  2. return arg;
  3. }

多个类型参数
定义泛型的时候,可以一次定义多个类型参数

  1. function swap<T, U>(tuple: [T, U]): [U, T] {
  2. return [tuple[1], tuple[0]];
  3. }
  4. swap([7, 'seven']); // ['seven', 7]

泛型约束
使用泛型时,由于不知道参数的类型,因此不能随意使用参数的属性。如下:

  1. function loggingIdentity<T>(arg: T): T {
  2. console.log(arg.length); // Error: T doesn't have .length
  3. return arg;
  4. }

如果想要使用参数的length属性, 只要传入的类型至少包含这一属性,ts便允许使用。 为此,我们需要列出对于T的约束要求。
为此,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:如果传入的参数不包含length属性便会报错

  1. interface Lengthwise {
  2. length: number;
  3. }
  4. function loggingIdentity<T extends Lengthwise>(arg: T): T {
  5. console.log(arg.length); // Now we know it has a .length property, so no more error
  6. return arg;
  7. }
  8. loggingIdentity(3); // Error, number doesn't have a .length property
  9. loggingIdentity({length: 10, value: 3});

在泛型约束中使用类型参数
你可以声明一个类型参数,且它被另一个类型参数所约束。 比如,现在我们想要用属性名从对象里获取这个属性。 并且我们想要确保这个属性存在于对象 obj上,因此我们需要在这两个类型之间使用约束。

  1. function getProperty(obj: T, key: K) {
  2. return obj[key];
  3. }
  4. let x = { a: 1, b: 2, c: 3, d: 4 };
  5. getProperty(x, "a"); // okay
  6. getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

泛型接口
使用含有泛型的接口来定义函数的形状

  1. interface GenericIdentityFn {
  2. <T>(arg: T): T;
  3. }
  4. function identity<T>(arg: T): T {
  5. return arg;
  6. }
  7. let myIdentity: GenericIdentityFn = identity;

进一步,我们可以把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary而不只是Dictionary)。 这样接口里的其它成员也能知道这个参数的类型了。

  1. interface GenericIdentityFn<T> {
  2. (arg: T): T;
  3. }
  4. function identity<T>(arg: T): T {
  5. return arg;
  6. }
  7. let myIdentity: GenericIdentityFn<number> = identity;

泛型类
除了泛型接口,我们还可以创建泛型类。 注意,无法创建泛型枚举和泛型命名空间
泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。

  1. class GenericNumber<T> {
  2. zeroValue: T;
  3. add: (x: T, y: T) => T;
  4. }
  5. let myGenericNumber = new GenericNumber<number>();
  6. myGenericNumber.zeroValue = 0;
  7. myGenericNumber.add = function(x, y) { return x + y; };

泛型参数的默认类型在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用

  1. function createArray<T = string>(length: number, value: T): Array<T> {
  2. let result: T[] = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }

函数

函数声明
函数声明后,typescript对输入输出都做了约束,调用该函数时不能多传也不能少传参数,或者传入参数的类型与约束的形参类型不同也是不被允许的。

  1. function sum(x: number, y: number): number {
  2. return x + y;
  3. }
  4. sum(2,3) // ok
  5. sum(2) // error
  6. sum(2,3,4) //error
  7. sum(2, '3') // error

函数表达式
函数表达式即是将声明的匿名函数复制给一个变量

  1. let mySum = function (x: number, y: number): number {
  2. return x + y;
  3. };
  4. // 或者手动给mySum添加类型
  5. let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
  6. return x + y;
  7. };
  8. // 也可以用接口来定义函数的形状
  9. interface sumFuc {
  10. (x: number, y: number): number;
  11. }
  12. let mySum : sumFuc
  13. mySum = function (x: number, y: number): number {
  14. return x + y;
  15. };

函数参数
前面提到了输入多余或者少输参数都会报错,我们可以通过可选参数或者剩余参数来解决这个问题

  1. function sum(x: number, y?: number): number {
  2. if(y) {
  3. return x + y;
  4. }
  5. return x;
  6. }
  7. sum(2,3) // 5
  8. sum(2) // 2

当设置可选参数时,可选参数必须接在必需参数后面,可选参数后面不能再出现必需参数,如果要解决可选参数必须接在必需参数后面这个限制,我们可以利用参数默认值来解决

  1. function sum(x: number = 2, y: number): number {
  2. return x + y;
  3. }
  4. sum(3, 5) // 8
  5. sum(undefined, 3) // 5
  6. sum(3) // error

如果不确定传入参数的个数,我们可以使用剩余参数

  1. function sum(x: number = 2, ...item: number[]): number {
  2. items.forEach(function(item) {
  3. x = x + item;
  4. });
  5. return x;
  6. }
  7. sum(2, 3, 4, 6, 8) // 23

函数重载
如果我们想要实现一个sum函数,当x类型为number时返回两者之和,当传入x为数组时,返回一个数组。因为需要函数接受不同的类型参数,我们可以使用联合类型来解决。

  1. function sum(x: number | number[], y: number): number | number[] {
  2. if (typeof x === 'number') {
  3. return x + y;
  4. }
  5. else {
  6. return x.map(function(item) {
  7. return y + item;
  8. });
  9. }
  10. }
  11. sum([1,2],3) // [4,5]
  12. sum(1,2) // 3

但是这样存在一个问题就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为数组的时候,输出也应该为数组。
那么我们可以使用函数重载来解决:重载允许一个函数接受不同数量或类型的参数时,作出不同的处理我们可以重复定义多次sum函数,最后才来实现函数。

  1. function sum(x: number, y: number): number
  2. function sum(x: number[], y: number): number[]
  3. function sum(x: number | number[], y: number): number | number[] {
  4. if (typeof x === 'number') {
  5. return x + y;
  6. }
  7. else {
  8. return x.map(function(item) {
  9. return y + item;
  10. });
  11. }
  12. }
  13. sum([1,2],3) // [4,5]
  14. sum(1,2) // 3

TypeScript会优先从最前面的函数定义开始匹配,所以多个函数定义如有包含关系,需要优先把精确的定义写在前面

函数中的this
我们来看下面的一个例子,最终打印出来undefined,因为此时speedRun中的this是指向windom,我们知道我们可以使用箭头函数改变this指向来解决这个问题,但是在ts中如果你给编译器设置了—noImplicitThis标记,便会警告你犯了一个错误,它会指出 this.speed里的this的类型为any。

  1. let jim = {
  2. name: 'jim',
  3. age: 20,
  4. speed: 50,
  5. runspeed: function(){
  6. return function(){
  7. return this.speed
  8. }
  9. }
  10. }
  11. let speedRun = jim.runspeed();
  12. let speed = speedRun() // undefined,

我们可以提供一个显式的 this参数。 this参数是个假的参数,它出现在参数列表的最前面,表示 this是Person类型的,而非any,这样就不会报错了。

  1. interface Person {
  2. name: string
  3. age: number
  4. speed: number
  5. runspeed(this: Person): () => number
  6. }
  7. let jim : Person = {
  8. name: 'jim',
  9. age: 20,
  10. speed: 50,
  11. runspeed: function(this: Person){
  12. return () => {
  13. return this.speed
  14. }
  15. }
  16. }
  17. let speedRun = jim.runspeed();
  18. let speed = speedRun() // 50

回调函数的this
在回调函数里this报错的情况,当你将一个函数传递到某个库函数里稍后会被调用时。 因为当回调被调用的时候,它们会被当成一个普通函数调用, this将为undefined.我们来看一个例子:

  1. interface UIElement{
  2. addClickListener(onclick: (this:void, e:Event) => void);
  3. }

上面的例子接口中 this:void 表明 onclick 函数应该是一个不需要 this 对象的函数。如果 onclick 指向的是一个成员函数而且函数需要使用 this 对象,那么则 onclick 需要通过箭头函数来实现。这样就不会报错,因为箭头函数不会捕获this,所以你总是可以把它们传给期望this: void的函数。

  1. class Handler{
  2. info:string;
  3. onClick = (e:Event) => { this.info = e.message }
  4. }
  5. let h = new Handler();
  6. uiElement.addClickListener(h.onClick);

缺点是每个 Handler对象都会创建一个箭头函数。 另一方面,方法只会被创建一次,添加到 Handler的原型链上。 它们在不同 Handler对象间是共享的。