Ecmascript实现中的范型

内容纲要

由于前端开发又大量用到了这些技术,这里把几年前基于ECMAScript 262-3标准的范型解释用最新的ECMASript 2018+标准再次诠释。

同其他强类型、预编译为二进制文件、面向对象的ECMAScript一样,范型也是较常用的一种编程方式。

语法

首先看新版ECMAScript 2018+的语法:

function indeex<T>(arg: T): T {
    console.log(arg);
    return arg;
}
let result = indeex<string>('hello generics');

这里,indeex函数没有做任何有用的事情,只是告诉我们泛型函数怎么写,就是尖括号内大写的T(其他也行,别较真…)。当调用这个函数的时候:传入一个和T类型相同类型的参数,返回一个相同类型T的值。其中T可以是任何类型,any, string, number, class, object, *int64, *float32 等。

要调用这个函数有两种方法:第一个是在我们明确说出类型的例子中演示的,在这个例子中是一个字符串,然后我们传递这个值。

第二种方法是使用ECMAScript的类型检测:

function indeex<T>(arg: T): T {
    console.log(arg);
    return arg;
}
let result = indeex('hello generics');

这里没有指定类型,但由于当调用函数传入参数时返回的是相同类型的值,ECMAScript的编译器直接返回与传入类型相同的类型就可以了, 这个过程被称为隐式转换,这是编译器自带的能力:

Ecmascript实现中的范型

AJAX请求

假设我们想要发出一个Ajax请求,返回一个JSON,我们可以将其解析为一个对象或类。该功能可能如下所示:

function getAsync(url: string) {
    return fetch(url)
        .then((response: Response) => response.json());
}

fetch API请求数据,用Promise进行异步调用,调用完成后,在得到服务器响应后调用json函数,返回的数据可以是任何你想要的:

Ecmascript实现中的范型

这个函数可以通用,可以不断的复用。当然也可以写一个处理错误的catch方法。

这样写的好处之一是类型检查,自动检测已知类型,比如获取电影的信息时:

class Movie { title: string; }

function getAsync<T>(url: string): Promise<T[]> {
    return fetch(url)
        .then((response: Response) => response.json());
}

getAsync<Movie>("/movies")
  .then(movies => {
    movies.forEach(movie => {
      console.log(movie.title);
    });
  });

getASync函数返回一个Promise,当Promise被解析时得到一个T类型的数组。编译器便能自动识别调用getAsync函数时使用的变量类型。

Ecmascript实现中的范型

即使我没有指定类型,也可以自动识别。

Ecmascript实现中的范型

知道了这些就可以开始使用范型了。

使用

先看范型的运行过程。

假设在获取请求时的服务器地址是/api/v1/,然后再调用某个属性的时候,不是将URL传递给函数,而是包含属性的对象(还有很多其他的方法)。

而现在需要的是这样的:

function getAsync<T>(arg: T): Promise<T[]> {
    return fetch(`/api/v1/${arg.resourceName}`)
        .then((response: Response) => response.json());
}

但是这么写,编译器会提示在类型T中不存在resourceName

Ecmascript实现中的范型

此时,函数无法知道T会是哪种类型和属性。

所以需要创建一个接口Resource,并让类型T实现这个接口:

interface Resource { resourceName: string; }

function getAsync<T extends Resource>(arg: T): Promise<T[]> {
    return fetch(`/api/v1/${arg.resourceName}`)
        .then((response: Response) => response.json());
}

接着,更改Movie类来实现接口Resource,再来调用getAsync函数:

class Movie implements Resource {
  title: string;
  resourceName: string = "movies";
}
getAsync<Movie>(new Movie())
  .then(movies => {
    movies.forEach(movie => {
      console.log(movie.title);
    });
  });

也可以使用类型检测,不指定类型:

getAsync(new Movie())
  .then(movies => {
    movies.forEach(movie => {
      console.log(movie.title);
    });
  });

用例

假设要写一个函数来调用Ajax请求,根据id属性返回需要的资源:

function getSingleAsync<T extends Resource>(arg: T): Promise<T> {
  return fetch(`/api/v1/${arg.resourceName}/${arg.id}`)
    .then((response: Response) => response.json());
}

同样的问题,编译器无法知道类型T是否具有id属性。需要创建另一个接口告诉泛型函数T类实现两个接口来解决这个问题:

class Movie implements Resource, Identifiable {
  title: string;
  resourceName: string = "movies";
  id: number;
}

function getSingleAsync<T extends Resource & Identifiable>(arg: T): Promise<T> {
  return fetch(`/api/v1/${arg.resourceName}/${arg.id}`)
    .then((response: Response) => response.json());
}

let movieToFind = new Movie();
movieToFind.id = 1;
getSingleAsync(movieToFind)
  .then(movie => {
    console.log(movie.title);
  });

这时,编译器不再报错,类型也能确定。

Ecmascript实现中的范型

code enjoy! ʕ•ᴥ•ʔ

作者:indeex

链接:http://indeex.cc

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


发表评论

您的电子邮箱地址不会被公开。