博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
谈谈 "JS 和 设计泛型"
阅读量:6892 次
发布时间:2019-06-27

本文共 4916 字,大约阅读时间需要 16 分钟。

"泛型", 计算机编程中, 一个必不可少的概念。


简单理解泛型

什么是泛型

泛型是程序设计语言的一种特性。通过参数化类型来实现在同一份代码上操作多种数据类型。

对于强类型语言来书, 提到参数,最熟悉不过的就是定义 function A 时有形参,然后调用 A 时传递实参。 指定一个表示类型的变量,用它来代替某个实际的类型用于编程,而后通过实际调用时传入类型 来对其进行替换,以达到一段使用泛型程序可以实际适应不同类型的目的。

注: 各种程序设计语言和其编译器、运行环境对泛型的支持均不一样

泛型解决的问题

  • 可重用性
  • 类型和算法安全
  • 效率

这是非泛型类和非泛型方法无法具备的

常见的情形

你有一个函数,它带有一个参数,参数类型是A,然而当参数类型改变成B的时候,你不得不复制这个函数

例如,下面的代码中第二个函数就是复制第一个函数——它仅仅是用String类型代替了Integer类型

func areIntEqual(x: Int, _ y: Int) -> Bool {  return x == y}func areStringsEqual(x: String, _ y: String) -> Bool {  return x == y}areStringsEqual("ray", "ray") // trueareIntEqual(1, 1) // true复制代码

通过采用泛型,可以合并这两个函数为一个并同时保持类型安全。下面是代码实现

// 用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替func areTheyEqual(x: T, _ y: T) -> Bool {  return x == y}areTheyEqual("ray", "ray")areTheyEqual(1, 1)复制代码

JavaScript和泛型的对应关系

泛型 和 模板方法(设计)模式

在一个系列的行为中,有一些是确定的,有一些是不明确的,我们把确定的行为定义在一个抽象类中, 不确定的行为定义为抽象方法,由具体的子类去实现,这种不影响整个流程,但可以应对各种情况的方法 就可以称之为模板方法模式

demo - Coffee or Tea

几个步骤:

  • 把水煮沸
  • 用沸水浸泡茶叶
  • 把茶水倒进杯子
  • 加柠檬
/* 抽象父类:饮料 */var Beverage = function(){};Beverage.prototype.boilWater = function() {  console.log("把水煮沸");};Beverage.prototype.brew = function() {  throw new Error("子类必须重写brew方法");};Beverage.prototype.pourInCup = function() {  throw new Error("子类必须重写pourInCup方法");};Beverage.prototype.addCondiments = function() {  throw new Error("子类必须重写addCondiments方法");};/* 模板方法 */Beverage.prototype.init = function() {  this.boilWater();  this.brew();  this.pourInCup();  this.addCondiments();}/* ------------分割线------------ *//* 实现子类 Coffee*/var Coffee = function(){};Coffee.prototype = new Beverage();// 重写非公有方法Coffee.prototype.brew = function() {  console.log("用沸水冲泡咖啡");};Coffee.prototype.pourInCup = function() {  console.log("把咖啡倒进杯子");};Coffee.prototype.addCondiments = function() {  console.log("加牛奶");};var coffee = new Coffee();coffee.init();/* 实现子类 Tea*/var Tea = function(){};Tea.prototype = new Beverage();// 重写非公有方法Tea.prototype.brew = function() {  console.log("用沸水冲泡茶叶");};Tea.prototype.pourInCup = function() {  console.log("把茶倒进杯子");};Tea.prototype.addCondiments = function() {  console.log("加柠檬");};var tea = new Tea();tea.init();复制代码

这里的Beverage.prototype.init就是所谓的模板方法

它作为一个算法的模板指导子类以何种顺序去执行哪些方法,在其内部,算法内的每一 个步骤都清楚的展示在我们眼前

泛型 和 TypeScript

  • 泛型函数
  • 泛型类

TypeScript 为 JavaScriopt 带来了强类型特性,但这就意味着限制了类型的自由度。同一段程序, 为了适应不同的类型,就可能需要写不同的处理函数

而且这些处理函数中所有逻辑完全相同,唯一不同的就是类型——这严重违反抽象和复用代码的原则

泛型函数

js源码

var service = {    getStringValue: function() {        return "a string value";    },    getNumberValue: function() {        return 20;    }};function middleware(value) {    console.log(value);    return value;}var sValue = middleware(service.getStringValue());var nValue = middleware(service.getNumberValue());复制代码

ts改写使用泛型

const service = {    getStringValue(): string {        return "a string value";    },    getNumberValue(): number {        return 20;    }};// 泛型方法改造function middleware
(value: T): T { console.log(value); return value;}var sValue = middleware(service.getStringValue());var nValue = middleware(service.getNumberValue());复制代码

middleware 后面紧接的 表示声明一个表示类型的变量,Value: T 表示声明参数是 T 类型的, 后面的 : T 表示返回值也是 T 类型的

到这里为止, TS改造之后的泛型方法和改造之前的js代码没什么区别。 现在的问题是 middleware 要怎么样定义才既可能返回 string,又可能返回 number,而且还能被类型检查正确推导出来? 如果不使用泛型方法要实现这个功能的代码实现:

第 1 个办法,用 any:

function middleware(value: any): any {    console.log(value);    return value;}复制代码

这个办法可以检查通过。但它的问题在于 middleware 内部失去了类型检查,在后在对 sValue 和 nValue 赋值的时候, 也只是当作类型没有问题。简单的说,是有“假装”没问题

第 2 个办法,多个 middleware:

function middleware1(value: string): string { ... }function middleware2(value: number): number { ... }复制代码

或者用 TypeScript 的重载(overload)来实现

function middleware(value: string): string;function middleware(value: number): number;function middleware(value: any): any {    // 实现一样没有严格的类型检查}复制代码

这种方法最主要的一个问题是……如果我有 10 种类型的数据,就需要定义 10 个函数(或重载), 那 20 个,200 个呢……

泛型类

即在声明类的时候声明泛型,那么在类的整个作用域范围内都可以使用声明的泛型类型, 多数 时候是应用于容器类

背景: 假设我们需要实现一个 FilteredList,我们可以向其中 add()(添加) 任意数据, 但是它在添加的时候会自动过滤掉不符合条件的一些,最终通过 get all() 输出所有符合条件的数据(数组)。 而过滤条件在构造对象的时候,以函数或 Lambda 表达式提供

// 声明泛型类,类型变量为 Tclass FilteredList
{ // 声明过滤器是以 T 为参数类型,返回 boolean 的函数表达式 filter: (v: T) => boolean; data: T[]; constructor(filter: (v: T) => boolean) { this.filter = filter; } add(value: T) { if (this.filter(value)) { this.data.push(value); } } get all(): T[] { return this.data; }}// 处理 string 类型的 FilteredListconst validStrings = new FilteredList
(s => !s);// 处理 number 类型的 FilteredListconst positiveNumber = new FilteredList
(n => n > 0);复制代码

甚至还可以把 (v: T) => boolean 声明为一个类型,以便复用:

type Predicate
= (v: T) => boolean;class FilteredList
{ filter: Predicate
; data: T[]; constructor(filter: Predicate
) { ... } add(value: T) { ... } get all(): T[] { ... }}复制代码

转载于:https://juejin.im/post/5c9d79086fb9a071012a2764

你可能感兴趣的文章
鲁棒性
查看>>
精品软件 推荐 微软官方出品的 杀毒软件 MSE Microsoft Security Essentials
查看>>
XenServer 用户管理 简介。
查看>>
[swift]-AFNetworking网络封装
查看>>
solr 简单的分布式布署
查看>>
如何建立多Master的SaltStack环境
查看>>
RHEL / CentOS Bash命令自动完成功能
查看>>
window 查询端口详细情况
查看>>
关于发送邮件日志显示450
查看>>
How to install pam_mysql in CentOS or Red Hat
查看>>
java8之lambda表达式(方法引用)
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
各类消息队列MQ比较
查看>>
Php设计模式(三):行为型模式 part 2
查看>>
截取中文字符串
查看>>
NoSQL 数据库
查看>>
TrueCrypt介绍及入门使用讲解【翻译】
查看>>
转向安防技术
查看>>