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的编译器直接返回与传入类型相同的类型就可以了, 这个过程被称为隐式转换,这是编译器自带的能力:

AJAX请求
假设我们想要发出一个Ajax请求,返回一个JSON,我们可以将其解析为一个对象或类。该功能可能如下所示:
function getAsync(url: string) {
return fetch(url)
.then((response: Response) => response.json());
}
用fetch API请求数据,用Promise进行异步调用,调用完成后,在得到服务器响应后调用json函数,返回的数据可以是任何你想要的:

这个函数可以通用,可以不断的复用。当然也可以写一个处理错误的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函数时使用的变量类型。

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

知道了这些就可以开始使用范型了。
使用
先看范型的运行过程。
假设在获取请求时的服务器地址是/api/v1/,然后再调用某个属性的时候,不是将URL传递给函数,而是包含属性的对象(还有很多其他的方法)。
而现在需要的是这样的:
function getAsync<T>(arg: T): Promise<T[]> {
return fetch(`/api/v1/${arg.resourceName}`)
.then((response: Response) => response.json());
}
但是这么写,编译器会提示在类型T中不存在resourceName。

此时,函数无法知道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);
});
这时,编译器不再报错,类型也能确定。

code enjoy! ʕ•ᴥ•ʔ
作者:indeex
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。