关于javascript:TypeScript专题之泛型

4次阅读

共计 5871 个字符,预计需要花费 15 分钟才能阅读完成。

泛型在其余很多语言中宽泛地失去应用,如 Java、C++、.Net、C# 等。它是程序设计语言的一种格调或范式。容许咱们在编写代码的时候应用一些当前才指定的类型,在实例化时作为参数指明这些类型。而不同语言对于泛型的实现是不同的。

泛型,即“参数化类型”。一提到参数,最相熟的就是定义方法时无形参,而后调用此办法时传递实参。那么参数化类型怎么了解呢?顾名思义,就是将类型由原来的具体的类型参数化,相似于办法中的变量参数,此时类型也定义成参数模式(能够称之为类型形参),而后在应用 / 调用时传入具体的类型(类型实参)。

泛型的实质是为了参数化类型(在不创立新的类型的状况下,通过泛型指定的不同类型来管制形参具体限度的类型)。也就是说在泛型应用过程中,操作的数据类型被指定为一个参数,这种参数类型能够用在类、接口和办法中,别离被称为泛型类、泛型接口、泛型办法。

为什么须要泛型

服务中间件解决数据

有这么一个场景:某个服务提供了一些不同类型的数据,咱们须要先通过一个中间件对这些数据进行一个根本的解决(比方验证,容错等),再对其进行应用。

JavaScript 实现:

// 模仿服务,提供不同的数据
const service = {getStringValue: function() {return 'a string value'},
  getNumberValue: function() {return 888}
}

// 解决数据的中间件。这里用 Log 来模仿解决,间接返回数据当作解决后的数据
function middleware(val) {console.log(val)
  return val
}

let sVal = middleware(service.getStringValue())
let nVal = middleware(service.getNumberValue())

那将下面的代码改写成 TypeScript 实现怎么去写?

首先看下 service 对象的代码,有两个办法,均有返回值。那么改写成 TypeScript 后应该是这样的:

const service = {getStringValue: function(): string {return 'a string value'},
  getNumberValue: function(): number {return 888}
}

为了保障 sValnVal的后续操作中类型查看的有效性,它们也会有类型(这里临时先以显示的形式定义其类型)。

const sVal: string = middleware(service.getStringValue())
const nVal: number = middleware(service.getNumberValue())

那么接下来的问题就是 middleware 这个函数了。它要怎么定义才既可能返回 string,又返回numer 类型的数据,而且还能被类型查看正确推导进去?

哎,这里有如下几种形式:

  • 用 any 办法,毛病也很显著,就是 middleware 办法外部失去了类型查看。

    function middleware(value: any) {console.log(value)
      return value
    }
  • 多个 middleware 办法,毛病是如果当初有 10 种类型的数据,就须要定义 10 个函数,那 200 个、500 个呢?没任何扩展性。

    function middleware1(value: string): string {...}
    function middleware2(value: number): number {...}

相似的办法重载也是一样:

function middleware(value: string): string;
function middleware(value: number): number;
function middleware(value: any): any {// 实现一样没有严格的类型查看}
  • 应用泛型

    function middleware<T>(value: T): T {console.log(value)
      return value
    }
  • 能够看出办法 middleware 后紧跟着 <T> 示意申明一个示意类型的变量;
  • value: T示意申明参数类型是 T 类型;
  • : T示意返回值也是 T 类型

那么在调用 middleware(service.getStringValue()) 的时候,因为参数推导进去是 string 类型的,所以这个时候类型 T 代表了 string,因而此时middleware 办法的返回值类型也是 string 类型。同理,当调用 middleware(service.getNumberValue()) 也是如此。
上述代码参照了另一位博主的文章解说

泛型类

后面曾经对泛型有个根本的意识了。下面例子中泛型的用法咱们称为 ” 泛型函数 ”。不过泛型更为广的用法是用于 ” 泛型类 ”——即在申明类的时候申明泛型,那么在类的整个个作用域范畴内都能够应用申明的泛型类型。

在 ES6 中,诸如 Promise、Map、Set 等,其实现跟泛型关系挺大的。如 Map 的定义:

interface Map<K, V> {clear(): void;
    delete(key: K): boolean;
    forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
    get(key: K): V | undefined;
    has(key: K): boolean;
    set(key: K, value: V): this;
    readonly size: number;
}

那接下来,咱们就来应用泛型来实现一个 List 类。实现了如下性能点:

  1. add: 存入一个元素;
  2. contains:查看是否蕴含某一元素;
  3. remove:删除某个元素;
  4. removeAll:删除所有元素;
  5. foreEach:遍历所有元素;
  6. map:遍历所有元素,对某些元素进行解决,返回新的汇合
class HcList<T> {private elems: T[] = []

  constructor(elems: T[] = []) {this.elems = elems}

  add(ele: T): void {this.elems.push(ele)
  }

  contains(ele: T): boolean {return this.elems.includes(ele)
  }

  remove(ele: T): void{this.elems = this.elems.filter(existing => existing !== ele)
  }

  removeAll(): void {this.elems = []
    this.elems.length = 0
  }

  forEach(func: (ele: T, index: number) => void): void {return this.elems.forEach(func)
  }

  map<U>(func: (ele: T, index: number) => U): HcList<U> {return new HcList<U>(this.elems.map(func))
  }
}

// test string
const stringList = new HcList<string>()
stringList.add('DarkCode')

// test number
const numberList = new HcList<number>()
numberList.add(88)

// test interface
interface IUser {
  name: string,
  age: number,
  sex?: number
}
const mUser: IUser = {
  name: 'huangche',
  age: 22
}
const userList = new HcList<IUser>()
userList.add(mUser)

接下来,为大家对泛型的深层了解,对这段代码进行一些重点的解释。

  • class HcList<T>:定义一个 HcList 的类,并接管一个名为 T 的通用类型参数,这个类型 T 可供所有该类的成员拜访
  • private elems: T[] = []:在类 HcList 中,应用了类型 T 来定义了公有属性elems,其类型是一个数组,是该类后续办法得以实现的根底
  • map<U>(func: (ele: T, index: number) => U): HcList<U>map办法须要一个本人的泛型类型参数,咱们须要在 map 办法的签名中定义一些 T 类型,并通过回调函数映射到 U 类型,因而另一个类型参数U。因为这些类型仅在其性能的 ” 气氛内 ”,并且它们之间不存在共享,因而不抵触。

泛型接口

接口是对类的一种形象,在接口中定义的办法和变量只有申明,不能在接口中实现。在 TypeScript 中,充沛正当地利用接口可能达到代码高度复用的成果,而对接口进行泛型类型约定,也是十分罕用的。往往在很多三方库的源码中看见。列如下面提到的map

interface Map<K, V> {clear(): void;
    delete(key: K): boolean;
    forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
    get(key: K): V | undefined;
    has(key: K): boolean;
    set(key: K, value: V): this;
    readonly size: number;
}

这里会基于泛型类说讲到的代码 HcList 来进行革新。

先定义一个 IList 接口,代码如下:

interface IList<T> {add(t: T): void,
  contains(t: T): boolean,
  remove(t: T): void,
  removeAll(): void,
  forEach(func: (t: T, index: number) => void): void,
  map<U>(func: (t: T, index: number) => U) : IList<U>
}

接下来写一个类 HcList 来实现这个接口,代码如下:

class HcListCls<T> implements IList<T>{private elems: T[] = []

  constructor(elems: T[] = []) {this.elems = elems}

  add(ele: T): void {this.elems.push(ele)
  }

  contains(ele: T): boolean {return this.elems.includes(ele)
  }

  remove(ele: T): void{this.elems = this.elems.filter(existing => existing !== ele)
  }

  removeAll(): void {this.elems = []
    this.elems.length = 0
  }

  forEach(func: (ele: T, index: number) => void): void {return this.elems.forEach(func)
  }

  map<U>(func: (ele: T, index: number) => U): HcList<U> {return new HcList<U>(this.elems.map(func))
  }
}

// test string
const strList = new HcListCls<string>()
strList.add('DarkCode')

// test number
const numList = new HcListCls<number>()
numList.add(88)

// test interface
interface IUser {
  name: string,
  age: number,
  sex?: number
}
const iUser: IUser = {
  name: 'huangche',
  age: 22
}
const uList = new HcListCls<IUser>()
userList.add(iUser)

泛型束缚

在函数外部应用泛型变量的时候,因为当时不晓得它是哪种类型,所以不能随便的操作它的属性或办法:

function loggingIdentity<T>(arg: T): T {console.log(arg.length);
    return arg;
}

// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.

上例中,泛型 T 不肯定蕴含属性 length,所以编译的时候报错了。

这时,咱们能够对泛型进行束缚,只容许这个函数传入那些蕴含 length 属性的变量。这就是泛型束缚:

interface Lengthwise {length: number;}

function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length);
    return arg;
}

上例中,咱们应用了 extends 束缚了泛型 T 必须合乎接口 Lengthwise 的形态,也就是必须蕴含 length 属性。

此时如果调用 loggingIdentity 的时候,传入的 arg 不蕴含 length,那么在编译阶段就会报错了:

interface Lengthwise {length: number;}

function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length);
    return arg;
}

loggingIdentity(7);

// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.

多个类型参数之间也能够相互束缚:

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});

上例中,咱们应用了两个类型参数,其中要求 T 继承 U,这样就保障了 U 上不会呈现 T 中不存在的字段。

泛型束缚可能在咱们应用到泛型的时候,对于类型的限度起到很重要的作用。

总结

  • 泛型是 TypeScript 中十分外围的一个技术点,作为前端程序员,必须要去把握的一个点
  • 泛型是对类型的编程
正文完
 0