如何通俗的解释TypeScript 泛型
作者:浅笑· 发布时间:2024-04-10 16:18:31
概述
在 TypeScript 中我们会使用泛型来对函数的相关类型进行约束。这里的函数,同时包含 class 的构造函数,因此,一个类的声明部分,也可以使用泛型。那么,究竟什么是泛型?如果通俗的理解泛型呢?
什么是泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
通俗的解释,泛型是类型系统中的“参数”,主要作用是为了类型的重用。从上面定义可以看出,它只会用在函数、接口和类中。它和js程序中的函数参数是两个层面的事物(虽然意义是相同的),因为 typescript 是静态类型系统,是在js进行编译时进行类型检查的系统,因此,泛型这种参数,实际上是在编译过程中的运行时使用。之所以称它为“参数”,是因为它具备和函数参数一模一样的特性。
function increse(param) {
// ...
}
而类型系统中,我们如此使用泛型:
function increase<T>(param: T): T {
//...
}
当 param 为一个类型时,T 被赋值为这个类型,在返回值中,T 即为该类型从而进行类型检查。
编译系统
要知道 typescript 本身的类型系统也需要编程,只不过它的编程方式很奇怪,你需要在它的程序代码中穿插 js代码(在 ts 代码中穿插 js 代码这个说法很怪,因为我们直观的感觉是在 js 代码中夹杂了 ts 代码)。
编程中,最重要的一种形式就是函数。在 typescript 的类型编程中,你看到函数了吗?没有。这是因为,有泛型的地方就有函数,只是函数的形式被 js 代码给割裂了。typescript 需要进行编译后得到最终产物。编译过程中要做两件事,一是在内存中运行类型编程的代码,从而形成类型检查体系,也就是说,我们能够对 js 代码进行类型检查,首先是 typescript 编译器运行 ts 编程代码后得到了一个运行时的检查系统本文来自否子戈的播客,运行这个系统,从而对穿插在其中的 js 代码进行类型断言;二是输出 js,输出过程中,编译系统已经运行完了类型编程的代码,就像php代码中 echo js 代码一样,php代码已经运行了,显示出来的是 js 代码。
从这个角度看 typescript,你或许更能理解为什么说它是JavaScript的超集,为什么它的编译结果是 js。
通俗的理解泛型
既然我们理解了 ts 编译系统的逻辑,那么我们就可以把类型的编程和 js 本身的业务编程在情感上区分开。我们所讲的“泛型”,只存在于类型编程的部分,这部分代码是 ts 的编译运行时代码。
我们来看下一个简单的例子:
function increase<T>(param: T): T {
//...
}
这段代码,如果我们把 js 代码区分开,然后用类型描述文本来表示会是怎样?
// 声明函数 @type,参数为 T,返回结果为 (T): T
@type = T => (T): T
// 运行函数得到一个类型 F,即类型为 (number): number
@F = @type(number)
// 要求 increase 这个函数符合 F 这种类型,也就是参数为 number,返回值也为 number
@@F
function increase(param) {
// ...
}
@@end
实际上没有 @@F 这种语法,是我编造出来的,目的是让你可以从另一个角度去看类型系统。
当我们理解泛型是一种“参数”之后,我们可能会问:类型系统的函数在哪里?对于 js 函数而言,你可以很容易指出函数声明语句和参数,但是 ts 中,这个部分是隐藏起来的。不过,我们可以在一些特定结构中,比较容易看到类型函数的影子:
// 声明一个泛型接口,这个写法,像极了声明一个函数,我们用描述语言来形容 @type = T => (T): T
interface GenericIdentityFn<T> {
(arg: T): T;
}
// 这个写法,有点像一个闭包函数,在声明函数后,立即运行这个函数,描述语言:@@[T => (T): T](any)
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型接口,像极了调用一个函数,我们用描述语言来形容 @type(number)
let myIdentity: GenericIdentityFn<number> = identity;
上面这一整段代码,我们用描述文本重写一遍:
@GenericIdentityFn = T => (T): T
@@[T => (T): T](any)
function identify(arg) {
return arg
}
@@end
@@GenericIdentityFn(number)
let myIdentity = identity
@@end
我们在类型系统中声明了两个函数,分别是 @GenericIdentityFn 和 @some(匿名函数 @[T => (T): T])。虽然是两个函数,但是实际上,它们的是一模一样的,因为 typescript 是结构类型,也就是在类型检查的时候只判断结构上的每个节点类型是否相同,而不是必须保持类型变量本身的指针相同。@GenericIdentityFn 和 @some 这两个函数分别被调用,用来修饰 identify 和 myIdentify,在调用的时候,接收的参数不同,所以导致最终的类型检查规则是不同的,identify 只要保证参数和返回值的类型相同,至于具体什么类型,any。而 myIdentify 除了保证参数返回值类型相同外,还要求类型必须是 number。
泛型类
除了泛型接口,class 类也可以泛型化,即“泛型类”,借助泛型类,我们来探究一下泛型的声明和使用的步骤。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
前文泛型接口因为只是为了约束函数的类型,所以写的很像函数,实际上,我们可以用描述语言重新描述一个泛型接口和泛型类。上面的红色部分,我们用描述语言来描述:
@GenericNumber = T => class {
zeroValue: T;
add: (x: T, y: T) => T;
}
@GenericNumber 这个函数,以 T 为参数,返回一个 class,在 @type 函数体内多次用到了参数 T。
@GenericIdentityFn = T => interface {
(arg: T): T;
}
我们重新描述了前面的 interface GenericIdentityFn,这样我们就可以在接口中增加其他的方法。
可以注意到,即使 typescript 内置的基础类型,例如 Array,被声明为泛型接口、泛型类之后,这些接口和类在使用时必须通过<>传入参数,本质上,因为它们都是函数,只是返回值不同。
其他泛型使用的通俗解释
接下来我们要再描述一个复杂的类型:
class Animal {
numLegs: number;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
我们姑且不去看 new() 的部分,我们看尖括号中的 extends语法,这里应该怎么理解呢?实际上,我们面对的问题是,在编译时,<A extends Animal> 尖括号中的内容是什么时候运行的,是之前,还是之间?
// 到底是
@type = (A extends Animal) => (new() => A): A
@type(T)
// 还是
@type = A => (new() => A): A
@type(T extends Animal)复
因为 typescript 是静态类型系统,Animal 是不变的类,因此,可以推测其实在类的创建之前,尖括号的内容已经被运行了。
@type = (A extends Animal) => (new() => A): A
也就是说,要使用 @type(T) 产生类型,首先 T 要满足 Animal 的结构,然后才能得到需要的类型,如果 T 已经不满足 Animal 类的结构了,那么编译器会直接报错,而这个报错,不是类型检查阶段,而是在类型系统的创建阶段,也就是 ts 代码的运行阶段。这种情况被称为“泛型约束”。
另外,类似 <A,B> 这样的语法其实和函数参数一致。
@type = (A, B) => (A|B): SomeType
我们再来看 ts 内置的基础类型:Array<number>
@Array = any => any[]
来源:https://www.cnblogs.com/qianxiaox/p/14054540.html


猜你喜欢
- 首先通过一个例子来看一下本文中可能用到的对象和相关概念。 #coding: UTF-8 import sys # 模块,sys指向这个模块对
- 录入身份证信息是一件繁琐的工作,如果可以自动识别并且录入系统,那可真是太好了。今天我们就来学习一下,如何自动识别身份证信息并且录入系统~识别
- 序言这不是圣诞节快到了,准备让让女朋友开心开心,也算是亲手做的,稍稍花了点心思。话不多说,咱们直接来展示吧,学会了赶紧画给你的那个她吧!本文
- 这篇分享几个在地址栏实现的Javascript有趣效果和应用。能在浏览器地址栏实现的效果太多了,字体放大、显示所有图片、显示Cookie等等
- 前序在Python中,集合(Set) 是一个无序、不重复的序列,它不支持索引。集合一般用于元素去重或者一些数学中的操作像union,inte
- 保存模型保存模型仅仅是为了测试的时候,只需要torch.save(model.state_dict, path)path 为保存的路径但是有
- 双系统配置及MySQL数据库存储情境:Windows XP下d:\mysql\data中存有MySQL数据库,Linux系统为Ubuntu
- 需求:从一台Oracle数据库获取数据,本以为是很简单的事情,直接将原来的SqlClient换成OracleClient调用,结果远没自己想
- 本文将遍历批量数据点并让TensorFlow更新斜率和y截距。这次将使用Scikit Learn的内建iris数据集。特别地,我们将用数据点
- 1.查看当前电脑python版本python -V // 显示2.7.x2.用brew升级pythonbrew update p
- 如下所示:后台关键代码:data = {}#keys与values分别为该数据的键数组,值的数组。这里循环为字典添加对应键值for k, v
- 要做一个页面上短信息的提示音的功能,本来想用HTML5中Audio+IE下的bgsound来实现,可是发现每种浏览器对Audio的解码类型还
- 1.优化你的MySQL查询缓存 在MySQL服务器上进行查询,可以启用高速查询缓存。让数据库引擎在后台悄悄的处理是提高性能的最有效方法之一。
- import numpy as npimport pandas as pdimport matplotlib.pylab as pltif
- 本文实例讲述了Python实现的本地文件搜索功能。分享给大家供大家参考,具体如下:偶尔需要搜索指定文件,不想每次都在windows下面去搜索
- 装饰器信号与槽所谓装饰器信号与槽,就是通过装饰器的方法来定义信号与槽函数,具体的使用方法如下@PyQt5.QtCore.pyqtSlot(参
- reduce()函数也是Python内置的一个高阶函数。reduce()格式:reduce (func, seq[, init()])red
- asp自定义错误显示方法:<html><head><meta http-equiv="Co
- 接前两篇“运行时查看线程信息”的博客,我在想,既然我可以随时打印线程信息,那么我是不是可以随时打印进程内部的其它信息呢?比如,实时查看一些对
- 前言:这篇文章给大家介绍了怎样用python创建一个简单的报警,它可以运行在命令行终端,它需要分钟做为命令行参数,在这个分钟后会打印”wak