一.TypeScript介绍
1.TypeScript概念
(1).TypeScript(简称:TS)是JavaScript的超集(JS有的TS都有)
(2).TypeScript = Type + JavaScript(在JS基础之上,为JS添加了类型支持)
(3).TypeScript是微软开发的开源编程语言,可以在任何运行JavaScript的地方运行。
2.TypeScript为什么要为JS添加类型支持?
背景: JS的类型系统存在"先天缺陷", JS代码中绝大部分错误都是类型错误(Uncaught TypeError)。
问题: 增加了找Bug, 改Bug的时间,严重影响开发效率
从编程语言的动静区分, TypeScript属于静态类型的编程语言,JS属于动态类型的编程语言。
静态类型: 编译期做类型检查;
动态类型: 执行期做类型检查。
代码编译和代码执行的顺序: 1.编译 2.执行。
对于JS来说: 需要等到代码真正去执行的时候才能发现错误(晚)。
对于TS来说: 在代码编译的时候(代码执行前)就可以发现错误(早)。
并且,配合VSCode等开发工具,TS可以提前到在编写代码的同时就发现代码中错误,减少找Bug, 改Bug时间。
3.TypeScript相比JS的优势
(1). 更早(写代码的同时)发现错误,减少找Bug, 改Bug时间,提升开发效率。
(2).程序中任何位置的代码都有代码提示,随时随地的安全感,增强了开发体验。
(3).强大的类型系统提升了代码的可维护性,使得重构代码更加容易。
(4).支持最新的ECMAScript语法,优先体验最新的语法, 让你走在前端技术的最前沿。
(5).TS类型推断机制,不需要再代码中每个地方都显示标注类型,让你在享受优势的同时,尽量降低了成本。
(6).Vue3源码使用TS重写,Angular默认支持TS, React与TS完美配合,TypeScript已成为大中型前端项目的首先编程语言。
二.TypeScript初体验
1.安装编译TS的工具包
问题: 为什么要安装编译TS的工具包?
回答:Node.js/浏览器,只认识JS代码,不认识TS代码。需要先将TS代码转换为JS代码,然后才能运行。
安装命令:
npm i -g typescript
其中typescript包就是用来编译TS代码的包,提供了tsc命令,实现了TS -> JS的转化。
验证是否安装成功:
tsc -v
2.示例(编译并运行ts代码)
(1).创建hello.ts文件(注意: TS文件的后缀名为.ts)
console.log("hello ts")
(2).将TS便以为JS: 在终端中输入命令,
tsc hello.ts
此时,在同级目录中会出现一个同名的JS文件
(3).执行JS代码: 在终端输入命令,
node hello.js
说明: 所有合法的js代码都是ts代码,有js基础只需要学习ts的类型即可。
注意: 由TS编译生成的JS文件,代码中就没有类型信息了。
3.简化运行TS的步骤
问题描述: 每次修改代码后,都要重复执行两个命令,才能运行TS代码,太繁琐。
简化方式: 使用ts-node包,直接在Node.js中执行TS代码。
安装命令:
npm i -g ts-node
使用方式:
ts-node hello.ts
解释: ts-node命令在内部偷偷的将TS -> JS, 然后, 再运行JS代码。
三.TypeScript常用类型
1.概述
TypeScript是JS的超集,TS提供了JS的所有功能,并且额外的增加了: 类型系统。
- 所有的JS代码都是TS代码
- JS有类型(比如, number/string等),但是JS不会检查变量的类型是否发生变化。而TS会检查。
TypeScript类型系统的主要优势: 可以显示标记出代码中的意外行为,从而降低了发生错误的可能性。
1. 类型注解
2. 常用基础类型
2.类型注解
示例
let age: number = 18
说明: 代码中的:number就是类型注解。
作用: 为变量添加类型约束。比如,上述代码中,约定变量age的类型为number(数值类型)。
解释: 约定了什么类型,就只能给变量赋值该类型的值,否则,就会报错。
3.常用基础类型
(1).概述
可以将TS中的常用基础类型细分为两类:
1. JS已有类型
- 原始类型: number/string/boolean/null/undefined/symbol
- 对象类型: object(包括数组,对象,函数等对象)
2. TS新增类型 - 联合类型,自定义类型(类型别名),接口,元组,字面量类型,枚举,void, any等。
(2).原始类型
number/string/boolean/null/undefined/symbol
特点: 简单。这些类型,完全按照js中类型的名称来书写
示例:
let age: number = 18
let myName: string = '刘老师'
let isLoading: boolean = false
let a: null = null
let b: undefined = undefined
let s: symbol = Symbol()
//等等...
(3).对象类型
object(包括数组,对象,函数等对象)
特点: 对象类型,在TS中更加细化,每个具体的对象都有自己的类型语法。
- 数据类型两种写法:(推荐使用number[]写法)
let numbers: number[] = [1, 3, 5]
let strings: Array<String> = ['a', 'b', 'c']
(4).联合类型
需求: 数组中既有number类型,又有string类型,这个数组的类型应该如何写?
let arr:(number|string)[] = [1, 'a', 3, 'b']
解释: | (竖线)在TS中叫做联合类型(由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种)。
注意: 这是TS中联合类型的语法,只有一根竖线,不要与JS中的或(||)混淆了。
(5).类型别名
为任意类型起别名。
使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用.
示例:
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]
解释:
1. 使用type关键字来创建类型别名
2. 类型别名(比如,此处的CustomArray),可以是任意合法的变量名称。
3. 创建类型别名后,直接使用该类型别名作为变量的类型注解即可。
(6).函数类型
有返回值
函数的类型实际上指的是: 函数参数和返回值的类型。
为函数指定类型的两种方式:
1.单独指定参数, 返回值的类型:
function add(num1:number, num2:number):number{
return num1 + num2
}
const add2 = (num1:number, num2:number):number =>{
return num1 + num2
}
2.同时指定参数,返回值的类型
const add3:(num1:number, num2:number) => number = (num1, num2)=>{
return num1 + num2
}
解释: 当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型。
注意: 这种形式只适用于函数表达式。
无返回值
如果函数没有返回值,那么,函数返回值类型为: void
function greet(name: string): void {
console.log('hello', name)
}
可选参数
使用函数实现某个功能时,参数可以传也可以不传。这种情况下,在给函数参数指定类型时,就用到了可选参数了。
比如: 数组的slice方法,可以slice()也可以slice(1)还可以slice(1, 3)。
function mySlice(start?: number, end?: number): void {
console.log('起始索引:', start, '结束索引:', end)
}
可选参数: 在可传可不传的参数名称后面添加?(问号)
注意: 可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数。
(7).对象类型
JS中的对象是有属性和方法构成的,而TS中对象类型就是在描述对象的结构(有什么类型的属性和方法)。
对象类型的写法:
let person: { name: string; age: number; sayHi(): void } = {
name: 'zz',
age: 18,
sayHi() {
console.log('hi ', name)
}
}
解释:
1.直接使用{}来描述对象结构。属性采用属性名: 类型的形式,方法采用方法名():返回值类型的形式。
2.如果方法有参数,就在方法名后面的小括号中指定参数类型(比如: greet(name:string):void)
3.在一行代码中指定对象的多个属性类型时,使用;(分号)来分割。
- 如果一行代码只指定一个属性类型(通过换行来分隔多个属性类型),可以去掉;(分号)。
- 方法的类型也可以使用箭头函数形式(比如: {satHi:() =>void})。
可变属性
对象的属性或方法,也可以是可选的,此时就用到了可选属性了。
比如, 我们在使用axios({ … })时,如果发生GET请求,method属性就可以省略。
function myAxios(config:{url:string, method?:string}):void{
console.log(config)
}
可选属性的语法与函数可选参数的语法一致,都使用?(问号)来表示。
(8).接口
当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的
示例:
interface IPerson {
name: string
age: number
sayHi(): void
}
let person: IPerson = {
name: 'jack',
age: 19,
sayHi() { }
}
解释:
1.使用interface关键字来声明接口
2.接口名称(比如,此处的IPerson),可以是任意合法的变量名称。
3.声明接口后,直接使用接口名称作为变量的类型。
4.因为每一行只有一个属性类型,因此,属性类型后没有;(分号)
interface(接口)和type(类型别名)的对比:
- 相同点: 都可以给对象指定类型
- 不同点:
1. 接口,只能为对象指定类型
2. 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名。
接口继承
如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用。
比如,这两个接口都有x, y两个属性,重复写两次,可以,但很繁琐。
interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }
更好的方式:
interface Point2D { x: number; y: number }
interface Point3D extends Point2D { z: number }
解释:
1. 使用extends(继承)关键字实现了接口Point3D继承Point2D
2. 继承后,Point3D就有了Point2D的所有属性和方法(此时,Point3D同时有x, y, z三个属性)。
(9).元组
场景: 在地图中,使用经纬度坐标标记位置信息。
可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型。
let position: number[] = [39.5427, 116.2317]
使用number[]的缺点: 不严谨,因为该类型的数组中可以出现任意多个数字。
更好的方式: 元组(Tuple)
元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对象的类型。
let position: [number, number] = [39.5427, 116.2317]
解释:
1. 元组类型可以确切地标记出有多少个元素,以及每个元素的类型。
*2.*该示例中,元素有两个元素,每个元素的类型都是number.
(10).类型推论
在TS中,某些没有明确指出类型的地方,TS的类型推论机制会帮助提供类型。
换句话说: 由于类型推论的存在,这些地方,类型注解可以省略不写!
1. 声明变量并初始化时
let age = 18
2. 决定函数返回值
function add(num1:number, num2:number){
return num1 + num2
}
注意: 这两种情况下,类型注解可以省略不写!
推荐: 能省略类型注解的地方就省略(偷懒,充分利用TS类型推论的能力,提升开发效率)。
技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用VSCode的提示来查看类型。
(11).类型断言
有时候你会比TS更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。
比如:
<a href="http://baidu.com" id="link">百度</a>
const aLink: HTMLElement
const aLink = document.getElementById('link')
注意: getElementById方法返回值类型是HTMLElement, 该类型只包含所有标签公共的属性或方法,不包含a标签特有的href等属性。
因此,这个类型太宽泛(不具体),无法操作href等a标签特有的属性或方法。
解决方式: 这种情况下就需要使用类型断言指定更加具体的类型。
使用断言:
const aLink = document.getElementById('link') as HTMLAnchorElement
解释:
1. 使用as关键字实现类型断言
*2.*关键字as后面的类型是一个更加具体的类型(HTMLAnchorElement是HTMLElement的子类型)。
*3.*通过类型断言,aLink的类型变得更加具体,这样就可以访问a标签特有的属性或方法了。
另一种语法,使用<>语法,这种语法形式不常用知道即可:
let aLinks = <HTMLAnchorElement>document.getElementById('link')
技巧: 在浏览器控制台,通过console.dir()打印DOM元素,在属性列表的最后面,即可看到该元素的类型。
(12).字面量类型
思考一下代码,两个变量的类型分别是什么?
let str1 = 'Hello TS'
const str2 = 'Hello TS'
通过TS类型推论机制,可以得到答案:
1. 变量str1的类型为: string
2. 变量str2的类型为: ‘Hello TS’
解释:
1. str1是一个变量(let), 它的值可以是任意字符串,所以类型为: string.
2. str2是一个常量(const), 它的值不能变化只能是’Hello TS’, 所以, 它的类型为: ‘Hello TS’
注意:此处的’Hello TS’, 就是一个字面量类型。也就是说某个特定的字符串也可以作为TS中的类型。
除字符串外,任意的JS字面量(比如,对象,数字等)都可以作为类型使用。
const str2:'Hello TS' = 'Hello TS'
let age: 18 = 18
使用模式: 字面量类型配合联合类型一起使用。
使用场景: 用来表示一组明确的可选列表
比如,在贪吃蛇游戏中,游戏的方向的可选值只能是上,下,左,右中的任意一个。
function changeDirection(direction: 'up' | 'down' | 'left' | 'right') {
console.log(direction)
}
解释: 参数diection的值只能是up/down/left/right中的任意一个。
优势: 相比于string类型,使用字面量类型更加精确,严谨。
(13).枚举
枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值。
枚举: 定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个。
enum Direction {UP, Down, Left, Right}
function changeDirection(direction:Direction){
console.log(direction)
}
解释:
1.使用enum关键字定义枚举
2.约定枚举名称,枚举中的值以大写字母开头。
3.枚举中的多个值之间通过(逗号)分隔。
4.定义好枚举后,直接使用枚举名称作为类型注解。
注意: 形参direction的类型为枚举Direction, 那么,实参的值就应该是枚举Direction成员的任意一个。
访问枚举成员:
enum Direction {UP, Down, Left, Right}
function changeDirection(direction:Direction){
console.log(direction)
}
changeDirection(Direction.UP)
解析: 类似于JS中的对象,直接通过点(.)语法访问枚举的成员。
数字枚举
问题: 我们把枚举成员作为了函数的实参,它的值是什么呢?
解释: 通过将鼠标移入Direction.UP, 可以看到枚举成员UP的值为0。
注意: 枚举成员是有值的,默认为: 从0开始自增的数值。
我们把,枚举成员的值为数字的枚举,称为: 数字枚举。
当然,也可以给枚举中的成员初始化值。
// Down -> 11, Left -> 12, Right -> 13
enum Direction {UP = 10, Down, Left, Right}
enum Direction {UP = 2, Down = 4, Left = 8, Right = 16}
字符串枚举
枚举成员的值是字符串
enum Direction {UP = 'UP', Down = 'DOWN', Left = 'LEFT', Right = 'RIGHT'}
注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值。
枚举的特点与原理
枚举是TS为数不多的非JavaScript类型级扩展(不仅仅是类型)的特性之一。
因为: 其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)
也就是说, 其他的类型会在编译为js代码时自动移除。但是,枚举类型会被编译为JS代码!
说明: 枚举与前面讲到的字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表。
一般情况下,推荐使用字面量类型+联合类型组合的方法,这种方法更加直观,简洁,高效。
(14).any类型
原则: 不推荐使用any! 这会让TypeScript变为"AnyScript"(失去TS类型保护优势)。
因为当值的类型为any时,可以对该值进行任何操作,并且不会有代码提示。
let obj: any = { x: 0 }
obj.bar = 100
obj()
const n: number = obj
解释:以上操作都不会有任何类型错误提示,即使可能存在错误!
尽可能的避免使用any类型,除非临时使用any来避免书写很长,很复杂的类型!
其他隐式具有any类型的情况:
1.声明变量不提供类型也不提供默认值
2.函数参数不加类型
注意: 因为不推荐使用any,所以,这两种情况下都应该提供类型!
(15).typeof
众所周知, JS中提供了typeof操作符,用来在JS中获取数据的类型。
console.log(typeof "Hello world") // 打印string
实际上, TS也提供了typeof操作符: 可以在类型上下文中引用变量或属性的类型(类型查询)。
使用场景: 根据已有变量的值,获取该值的类型,来简化类型书写。
let p = {x:1, y:2}
function formatPoint(point:{x:number, y:number}){}
formatPoint(p)
function formatPoints(point: typeof p){}
解释:
1.使用typeof操作符来获取变量p的类型,结果与第一种(对象字面量形式的类型)相同。
2.typeof出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于JS代码)
3.注意: typeof只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)
四.TypeScript高级类型
1.概述
TS中的高级类型有很多,重点学习以下高级类型:
(1).class类
(2).类型兼容性
(3).交叉类型
(4).泛型和keyof
(5).索引签名类型和索引查询类型
(6).映射类型
2.class类
TypeScript全面支持ES2015中引入的class关键字,并为其添加了类型注解和其他语法(比如,可见性修饰符等).
class基本使用
class Person { }
let p1: Person
p1 = new Person()
解释:
1.根据TS中的类型推论,可以知道Person类的实例对象p的类型是Person。
2.TS中的class, 不仅仅提供了class的语法功能,也作为一种类型的存在。
实例对象初始化
class Person {
age: number
gender = '男'
}
let p1: Person
p1 = new Person()
p1.age = 18
console.log(p1)
解释:
1.声明成员age, 类型为number(没有初始值)
2.声明成员gender, 并设置初始值,此时,可省略类型注解(TS类型推论为string类型)。
构造函数
class Person {
age: number
gender: string
constructor(age: number, gender: string) {
this.age = age;
this.gender = gender
}
}
let p1: Person
p1 = new Person(18, '女')
p1.age = 18
console.log(p1)
解释:
1.成员初始化(比如, age:number)后,才可以通过this.age来访问实例成员。
2.需要为构造函数指定类型注解,否则会被隐式推断为any;构造函数不需要返回值类型。
实例方法
class Points {
x = 10
y = 10
scale(n: number): [number, number] {
this.x *= n;
this.y *= n;
return [this.x, this.y]
}
}
let pos = new Points()
let array = pos.scale(10)
console.log(array[0] + '---' + array[1])
解释: 方法的类型注解(参数和返回值)与函数用法相同。
继承
类继承的两种方式:
[1].extends(继承父类)
说明: JS中只有extends, 而implements是TS提供的
class Animal {
move() {
console.log('Moving along')
}
}
class Cat extends Animal {
bark() {
console.log('喵')
}
}
const cat = new Cat()
cat.move()
cat.bark()
解释:
1.通过extends关键字实现继承
2.子类Cat继承父类Animal, 则Cat的实例对象cat就同时具有了父类Animal和子类Cat
[2].implements(实现接口)
interface Singable {
sing(): void
}
class Person1 implements Singable {
sing(): void {
console.log('z....')
}
}
let c: Singable = new Person1();
c.sing()
解释:
1.通过implements关键字让class实现接口
2.Person类实现接口Singable意味着,Person类必须提供Singable接口中指定的所有方法和属性。
类型成员可见性
类成员可见性: 可以使用TS来控制class的方法或属性对于class外的代码是否可见。
可见性修饰符包括:
[1].public(公有的)
public: 表示公有的,公开的,公有成员可以被任何地方访问,默认可见性。
class Animal{
public move(){
console.log('Moving along')
}
}
解释:
1.在类属性或方法前面添加public关键字,来修饰该属性或方法是共有的。
2.因为public是默认可见性,所以,可以直接省略。
[2].protected(受保护的)
protected: 表示受保护的,仅对其声明所在的类和子类中(非实例对象)可见。
class Animal{
protected move(){
console.log('Moving along!')
}
}
class Cat extends Animal{
bark(){
console.log('汪!')
this.move()
}
}
解释:
1.在类属性或方法前面添加protected关键字,来修饰该属性或方法是受保护的。
2.在子类的方法内部可以通过this来访问父类中受保护的成员,但是,对实例不可见!
[3].private(私有的)
private: 表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的。
class Animal{
private move(){
console.log('Moving along!')
}
walk(){
this.move()
}
}
解释:
1.在类属性或方法前面添加private关键字,来修饰该属性或发布方法是私有的。
2.私有的属性或方法只在当前类中可见,对子类和实例对象也都是不可见的!
[4].readonly(只读)
readonly: 表示只读,用来防止在构造函数之外对属性进行赋值。
class Person{
readonly age:number = 18
constructor(age:number){
this.age = age;
}
}
解释:
1.使用readonly关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法。
2.注意: 属性age后面的类型注解(比如,此处的number) 如果不加,则age的类型为18(字面量类型)
3.接口或者{}表示的对象类型,也可以使用readonly.
3. 类型兼容性
注意: 在结构化类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型,这种说法并不准确。
类兼容性
更准确的说法: 对于对象类型来说,y的成员至少与x相同,则x兼容y(成员多的可以赋值给少的)。
class Point{
x:number;
y:number
}
class Point3D{
x:number;
y:number;
z:number
}
const p:Point = new Point3D()
解释:
1.Point3D的成员至少与Point相同,则Point兼容Point3D
2.所以,成员多的Point3D可以赋值给成员少的Point.
接口兼容性
类似于class。并且,class和interface之间可以兼容
interface Point{
x:number;
y:number
}
interface Point2D{
x:number;
y:number
}
let p1:Point
let p2:Point2D = p1
interface Point3D{
x:number;
y:number;
z:number
}
let p3:Point3D
p2 = p3
interface Point2D{
x:number;
y:number
}
class Point3D{
x:number;
y:number;
z:number
}
let p3:Point2D = new Point3D()
函数兼容性
函数之间兼容性比较复杂,需要考虑:
1.参数个数
2.参数类型
3.返回值
[1].参数个数
参数多的兼容参数少的(或者说,参数少的可以赋值给多的)
type F1 = (a:number) => void
type F2 = (a:number, b:number) => void
let f1:F1
let f2:F2 = f1
const arr = ['a', 'b', 'c']
arr.forEach(()=>{})
arr.forEach((item)=>{})
解释:
1.参数少的可以赋值给参数多的,所以,f1可以赋值给f2。
2.数组forEach方法的第一个参数是回调函数,该示例中类型为:(value:string, index:number, array:string[]) => void。
3.在JS中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了TS中函数类型之间的兼容性。
4.并且因为回调函数是有类型的,所以,TS会自动推导出参数item, index, array的类型。
[2].参数类型
相同位置的参数要相同(原始类型)或兼容(对象类型)
type F1 = (a: number) => string
type F2 = (a: number) => string
let f1: F1
let f2: F2 = f1
interface Point2D{
x:number;
y:number
}
interface Point3D{
x:number;
y:number;
z:number;
}
type F3 = (p:Point2D) => void
type F4 = (p:Point3D) => void
let f1: F3
let f2: F4 = f1
解释:
函数类型F2兼容函数类型F1, 因为F1和F2的第一个参数类型相同。
[3].返回值类型
只关注返回值类型本身即可:
type F5 = () => string
type F6 = () => string
let f5:F5
let f6:F6 = f5
type F7 = () => {name:string}
type F8 = () => {name:string, age:number}
let f7:F7
let f8:F8 = f7
解释:
1.如果返回值类型是原始类型,此时两个类型要相同,比如,类型F5和F6
2.如果返回值类型是对象类型,此时成员多的可以赋值给成员少的,比如,类型F7和F8
4.交叉类型
交叉类型(&): 功能类似于接口继承(extends), 用于组合多个类型为一个类型(常用于对象类型)
示例
interface Person{name:string}
interface Concat{phone:string}
type PersonDetail = Person & Contact
let obj: PersonDetail = {
name:'jack',
phone:'18206186979'
}
解释: 使用交叉类型后,新的类型PersonDetail就同时具备了Person和Contact的所有属性类型。
相当于
type PersonDetail = {name:string; phone:string}
交叉类型(&)和接口继承(extends)的对比:
相同点: 都可以实现对象类型的组合。
不同点: 两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同
interface A {
fn: (value: number) => string
}
interface B extends A {
fn: (value: string) => string
}
上述代码会报错,需要改成如下形式:
interface A {
fn: (value: number) => string
}
interface B {
fn: (value: string) => string
}
type C = A & B
说明:
可以简单的理解为:
fn:(value:string|number) => string
5.泛型
泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用。常用于: 函数,接口,class中。
需求: 创建一个id函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)。
function id(value: number): number{ return value }
比如, id(10)调用以上函数就会直接返回10本身。但是,该函数只接收数值类型,无法用于其他类型。
为了能让函数能够接受任意类型,可以将参数类型修改为any。但是,这样就失去了TS的类型保护,类型不安全。
function id(value: any):any { return value }
泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用。
实际上,在C#和Java等编程语言中,泛型都是用来实现可复用组件的主要工具之一。
泛型函数
创建泛型函数:
function id<Type>(value: Type):Type{ return value }
解释:
1.语法: 在函数名称的后面添加<>(尖括号), 尖括号中添加类型变量,比如此处的Type
2.类型变量Type, 是一种特殊类型的变量,它处理类型而不是值。
3.该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)。
4.因为Type是一个类型,因此可以作为函数参数和返回值的类型,表示参数和返回值具有相同的类型。
5.类型变量Type, 可以是任意合法的变量名称。
.