TypeScript入门

目前angular、deno已经开始使用typescript,并且我们熟知的vue,在3.0也即将会使用typescript,可以说,前端领域,typescript会逐渐变为必备的技能,那么,为什么typescript变得越来越火呢?

网上有各种typescript和javascript的对比,那么在我的角度的理解,javascript是解释型(动态)语言,可以说是从上到下执行,在我们开发过程中,比如有语法错误等等,需要执行到这一行代码才能知道,而typescript则像写易语言那样生成exe时,需要静态编译,而静态编译这个过程,会把代码都检查一遍,看是否通过检测,最终才生成exe,typescript最终是也是编译成javascript原生代码的,只是在这个生成过程中,会进行各种检测,来检查代码是否符合语法啊规则啊,符合的话最终再编译成javascript,规范了我们代码的编写,同时也提高了代码的复用以及组件化,在runtime阶段为我们提前找到错误。

typescript支持es5/es6的语法,并且扩展了javascript语法,更像java、c#、swift这种语言了。

在前端nodejs很火,但是为什么在后端却不火,很大程度也是因为nodejs也是解释型(动态)语言,优势就是解释型语言比较灵活,但是缺点也很明显,用node开发后台程序,开发一直爽,重构火葬场.一旦重构了,就会出现很多问题,像Java、c#这类语言,非常严谨,类型检查等非常严谨,而javascript呢,一般是靠我们用肉眼去排查,很麻烦,typescript就是解决这一类问题的。

总而言之,typescript是未来的趋势,也是谷歌推荐的框架,我也是刚学typescript,很多都是站在前辈的肩膀总结的,废话不多说,我们开始进入正题吧!


TypeScript 安装

首先我们全局安装

npm i typescript -g

全局安装完成后,我们新建一个hello.ts的ts文件

1
2
// hello.ts内容
let a = "TypeScript"

接下来我们在命令行输入tsc hello.ts来编译这个ts文件,然后会在同级目录生成一个编译好了的hello.js文件

1
2
// hello.js内容
var = "TypeScript"

那么我们每次都要输tsc hello.ts命令来编译,这样很麻烦,能否让它自动编译?答案是可以的,我平时使用vscode来开发,需要配置一下vscode就可以。

首先我们在命令行执行tsc --init来生成配置文件,然后我们在目录下看到生成了一个tsconfig.json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{
"compilerOptions": {
/* Basic Options */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */

/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */

/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */

/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}
}

这个json文件里有很多选项

  • target是选择编译到什么语法
  • module则是模块类型
  • outDir则是输出目录,可以指定这个参数到指定目录

接下来我们需要开启监控了,在vscode任务栏中



此时就会开启监控了,会监听ts的变化,然后自动去编译。


数据类型

java、c#是强类型语言,而js是弱类型语言,强弱类语言有什么区别呢?typescript最大的优点就是类型检查,可以帮你检查你定义的类型和赋值的类型。

布尔类型boolean

// 在js中,定义isFlag为true,为布尔类型boolean
let isFlag = true;
// 但是我们也可以重新给它赋值为字符串
isFlag = "hello swr";

// 在ts中,定义isFlag为true,为布尔类型boolean
// 在变量名后加冒号和类型,如  :boolean
let isFlag:boolean = true
// 重新赋值到字符串类型会报错
isFlag = "hello swr" 

// 在java中,一般是这样定义,要写变量名也要写类型名
// int a = 10; 
// string name = "TypeScript"

数字类型number

let age:number = 28;
age = 29;

字符串类型string

let name:string = "TypeScript"
name = "iamswr"

以上boolean、number、string类型有个共性,就是可以通过typeof来获取到是什么类型,是基本数据类型。

那么复杂的数据类型是怎么处理的呢?

数组 Array

// 在js中
let pets = ["旺财","小黑"];

// 在ts中
// 需要注意的是,这个是一个字符串类型的数组
// 只能往里面写字符串,写别的类型会报错
let pets:string[] = ["旺财","小黑"];

// 另外一种ts写法
let pets:Array<string> = ["旺财","小黑"];

// 那么如果想在数组里放对象呢?
let pets:Array<object> = [{name:"旺财"},{name:"小黑"}];

// 那么怎样在一个数组中,随意放string、number、boolean类型呢?
// 这里的 | 相当于 或 的意思
let arr:Array<string|number|boolean> = ["hello swr",28];

// 想在数组中放任意类型
let arr:Array<any> = ["hello swr",28,true]

元组类型tuple

什么是元组类型?其实元组是数组的一种。

let person:[string,number] = ['TypeScript',28]

有点类似解构赋值,但是又不完全是解构赋值,比如元组类型必须一一对应上,多了少了或者类型不对都会报错。

元组类型是一个不可变的数组,长度、类型是不可变的。

枚举类型enum

枚举在java中是从6.0才引入的一种类型,在java和ts中的关键字都是enum

什么是枚举?枚举有点类似一一列举,一个一个数出来,在易语言中,我们会经常枚举窗口,来找到自己想要的,一般用于值是某几个固定的值,比如生肖(有12种)、星座(有12种)、性别(男女)等,这些值是固定的,可以一个一个数出来。

为什么我们要用枚举呢?我们可以定义一些值,定义完了后可以直接拿来用了,用的时候也不会赋错值。

比如我们普通赋值

// 我们给性别赋值一个boy,但是我们有时候手误,可能输成boy1、boy2了
// 这样就会导致我们赋值错误了
let sex = "boy"

既然这样容易导致手误赋错值,那么我们可以定义一个枚举

// 定义一个枚举类型的值
enum sex {
  BOY,
  GIRL
}
console.log(sex)
console.log(`TypeScript是${sex.BOY}`)

我们看看转为es5语法是怎样的

// 转为es5语法
"use strict";
var sex;
(function (sex) {
    sex[sex["BOY"] = 0] = "BOY";
    sex[sex["GIRL"] = 1] = "GIRL";
})(sex || (sex = {}));
console.log(sex); // 打印输出{ '0': 'BOY', '1': 'GIRL', BOY: 0, GIRL: 1 }
console.log("\u90B5\u5A01\u5112\u662F" + sex.BOY); // 打印输出 TypeScript是0

是不是感觉有点像给对象添加各种属性,然后这个属性又有点像常量,然后通过对象去取这个属性?

上面这样写,不是很友好,那么我们还可以给BOY`GIRL`赋值

enum sex{
    BOY="男",
    GIRL="女"
}


// 转化为es5语法
// 我们顺便看看实现的原理
"use strict";
var sex;
// 首先这里是一个自执行函数
// 并且把sex定义为对象,传参进给自执行函数
// 然后给sex对象添加属性并且赋值
(function (sex) {
    sex["BOY"] = "\u7537";
    sex["GIRL"] = "\u5973";
})(sex || (sex = {}));
console.log(sex); // 打印输出 { BOY: '男', GIRL: '女' }
console.log("\u90B5\u5A01\u5112\u662F" + sex.BOY); // 打印输出 TypeScript是男

比如我们实际项目中,特别是商城类,订单会存在很多状态流转,那么非常适合用枚举

enum orderStatus {
    WAIT_FOR_PAY = "待支付",
    UNDELIVERED = "完成支付,待发货",
    DELIVERED = "已发货",
    COMPLETED = "已确认收货"
}

到这里,我们会有一个疑虑,为什么我们不这样写呢?

let orderStatus2 = {
    WAIT_FOR_PAY : "待支付",
    ...
}

如果我们直接写对象的键值对方式,是可以在外部修改这个值的,而我们通过enum则不能修改定义好的值了,更加严谨。

任意类型 any

any有好处也有坏处,特别是前端,很多时候写类型的时候,几乎分不清楚类型,任意去写,写起来很爽,但是对于后续的重构、迭代等是非常不友好的,会暴露出很多问题,某种程度来说,any类型就是放弃了类型检查了。。。

比如我们有这样一个场景,就是需要获取某一个dom节点

let btn = document.getElementById('btn');
btn.style.color = "blue";

此时我们发现在ts中会报错

因为我们取这个dom节点,有可能取到,也有可能没取到,当没取到的时候,相当于是null,是没有style这个属性的。

那么我们可以给它添加一个类型为any

// 添加一个any类型,此时就不会报错了,但是也相当于放弃了类型检查了
let btn:any = document.getElementById('btn');
btn.style.color = "blue";

// 当然也有粗暴一些的方式,利用 ! 强制断言
let btn = document.getElementById("btn");
btn!.style!.color = "blue";

// 可以赋值任何类型的值
// 跟以前我们var let声明的一模一样的
let person:any = "TypeScript"
person = 28

null undefined类型

这个也没什么好说的,不过可以看下下面的例子

// (string | number | null | undefined) 相当于这几种类型
// 是 string 或 number 或 null 或 undefined
let str:(string | number | null | undefined)
str = "hello swr"
str = 28
str = null
str = undefined

void类型

void表示没有任何类型,一般是定义函数没有返回值。

// ts写法
function say(name:string):void {
  console.log("hello",name)
}
say("swr")


// 转为es5
"use strict";
function say(name) {
    console.log("hello", name);
}
say("swr");

怎么理解叫没有返回值呢?此时我们给函数return一个值

function say(name:string):void {
  console.log("hello",name)
  // return"ok" 会报错
  return"ok"
  // return undefined 不会报错
  // return 不会报错
}
say("swr")

那么此时我们希望这个函数返回一个字符串类型怎么办?

function say(name:string):string {
  console.log("hello",name)
  return"ok"
}
say("swr")

never类型

这个用得很少,一般是用于抛出异常。

let xx:never;
function error(message: string): never {
  throw new Error(message);
}

error("error")

我们要搞明白any、never、void

  • any是任意的值
  • void是不能有任何值
  • never永远不会有返回值

any比较好理解,就是任何值都可以

let str:any = "hello swr"
str = 28
str = true

void不能有任何值(返回值)

function say():void {

}

never则不好理解,什么叫永远不会有返回值?

// 除了上面举例的抛出异常以外,我们看一下这个例子
// 这个loop函数,一旦开始执行,就永远不会结束
// 可以看出在while中,是死循环,永远都不会有返回值,包括undefined

function loop():never {
    while(true){
        console.log("陷入死循环啦")
    }
}

loop()

// 包括比如JSON.parse也是使用这种 never | any
function parse(str:string):(never | any){
    return JSON.parse(str)
}
// 首先在正常情况下,我们传一个JSON格式的字符串,是可以正常得到一个JSON对象的
let json = parse('{"name":"TypeScript"}')
// 但是有时候,传进去的不一定是JSON格式的字符串,那么就会抛出异常
// 此时就需要never了
let json = parse("iamswr")

也就是说,当一个函数执行的时候,被抛出异常打断了,导致没有返回值或者该函数是一个死循环,永远没有返回值,这样叫做永远不会有返回值。

实际开发中,是never和联合类型来一起用,比如

1
2
3
function say():(never | string) {
return "ok"
}


函数

函数是这样定义的

function say(name:string):void {
  console.log("hello",name)
}
say("TypeScript")

形参和实参要完全一样,如想不一样,则需要配置可选参数,可选参数放在后面

// 形参和实参一一对应,完全一样
function say(name:string,age:number):void {
  console.log("hello",name,age)
}
say("TypeScript",28)


// 可选参数,用 ? 处理,只能放在后面
function say(name:string,age?:number):void {
  console.log("hello",name,age)
}
say("TypeScript")

那么如何设置默认参数呢?

// 在js中我们是这样写的
function ajax(url,method="get"){
    console.log(url,method)
}

// 在ts中我们是这样写的
function ajax(url:string,method:string = "GET") {
  console.log(url,method)
}

那么如何设置剩余参数呢?可以利用扩展运算符

function sum(...args:Array<number>):number {
  returneval(args.join("+"))
}
let total:number = sum(1,2,3,4,5)
console.log(total)

那么如何实现函数重载呢?函数重载是java中非常有名的,在java中函数的重载,是指两个或者两个以上的同名函数,参数的个数和类型不一样

// 比如说我们现在有2个同名函数
function say(name:string){

}
function say(name:string,age:number){

}
// 那么我想达到一个效果
// 当我传参数name时,执行name:string这个函数
// 当我传参数name和age时,执行name:string,age:number这个函数
// 此时该怎么办?

接下来看一下typescript中的函数重载

// 首先声明两个函数名一样的函数
function say(val: string): void; // 函数的声明
function say(val: number): void; // 函数的声明
// 函数的实现,注意是在这里是有函数体的
// 其实下面的say()无论怎么执行,实际上就是执行下面的函数
function say(val: any):void {
  console.log(val)
}

say("hello swr")
say(28)

在typescript中主要体现是同一个同名函数提供多个函数类型定义,函数实际上就只有一个,就是拥有函数体那个,如果想根据传入值类型的不一样执行不同逻辑,则需要在这个函数里面进行一个类型判断。

那么这个函数重载有什么作用呢?其实在ts中,函数重载只是用来限制参数的个数和类型,用来检查类型的,而且重载不能拆开几个函数,这一点和java的处理是不一样的,需要注意。


如何定义一个类?

// ts写法
// 其实跟es6非常像,没太大的区别
class Person{
  // 这里声明的变量,是实例上的属性
  name:string
  age:number
  constructor(name:string,age:number){
    // this.name和this.age必须在前面先声明好类型
    // name:string   age:number
    this.name = name
    this.age = age
  }
  // 原型方法
  say():string{
    return"hello swr"
  }
}

let p = new Person("TypeScript",28)


// 那么转为es5呢?
"use strict";
var Person = /** @class */ (function () {
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.say = function () {
        return"hello swr";
    };
    return Person;
}());
var p = new Person("TypeScript", 28);

可以发现,其实跟我们es6的class是非常像的,那么类的继承是怎样实现呢?

// 类的继承和es6也是差不多
class Parent{
  // 这里声明的变量,是实例上的属性
  name:string
  age:number
  constructor(name:string,age:number){
    // this.name和this.age必须在前面先声明好类型
    // name:string   age:number
    this.name = name
    this.age = age
  }
  // 原型方法
  say():string{
    return"hello swr"
  }
}

class Child extends Parent{
  childName:string
  constructor(name:string,age:number,childName:string){
    super(name,age)
    this.childName = childName
  }
  childSay():string{
    return this.childName
  }
}

let child = new Child("TypeScript",28,"bb")
console.log(child)

类的修饰符

  • public公开的,可以供自己、子类以及其它类访问
  • protected受保护的,可以供自己、子类访问,但是其他就访问不了
  • private私有的,只有自己访问,而子类、其他都访问不了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Parent{
public name:string
protected age:number
private money:number

/**
* 也可以简写为
* constructor(public name:string,protected age:number,private money:number)
*/

constructor(name:string,age:number,money:number){
this.name = name
this.age = age
this.money = money
}
getName():string{
return this.name
}
getAge():number{
return this.age
}
getMoney():number{
return this.money
}
}

let p = new Parent("TypeScript",28,10)
console.log(p.name)
console.log(p.age) // 报错
console.log(p.money) // 报错

静态属性、静态方法,跟es6差不多

class Person{
    // 这是类的静态属性
    static name = "TypeScript"
    // 这是类的静态方法,需要通过这个类去调用
    static say(){
        console.log("hello swr")
    }
}
let p = new Person()
Person.say() // hello swr
p.say() // 报错

抽象类

抽象类和方法,有点类似抽取共性出来,但是又不是具体化,比如说,世界上的动物都需要吃东西,那么会把吃东西这个行为,抽象出来。

如果子类继承的是一个抽象类,子类必须实现父类里的抽象方法,不然的话不能实例化,会报错。

// 关键字 abstract 抽象的意思
// 首先定义个抽象类Animal
// Animal类有一个抽象方法eat
abstract class Animal{
    // 实际上是使用了public修饰符
    // 如果添加private修饰符则会报错
    abstract eat():void;
}

// 需要注意的是,这个Animal类是不能实例化的
let animal = new Animal() // 报错

// 抽象类的抽象方法,意思就是,需要在继承这个抽象类的子类中
// 实现这个抽象方法,不然会报错
// 报错,因为在子类中没有实现eat抽象方法
class Person extends Animal{
    eat1(){
        console.log("吃米饭")
    }
}

// Dog类继承Animal类后并且实现了抽象方法eat,所以不会报错
class Dog extends Animal{
    eat(){
        console.log("吃骨头")
    }
}

五、接口

这里的接口,主要是一种规范,规范某些类必须遵守规范,和抽象类有点类似,但是不局限于类,还有属性、函数等。

首先我们看看接口是如何规范对象的

// 假设我需要获取用户信息
// 我们通过这样的方式,规范必须传name和age的值
function getUserInfo(user:{name:string,age:number}){
    console.log(`${user.name}${user.age}`)
}
getUserInfo({name:"TypeScript",age:28})

这样看,还是挺完美的,那么问题就出现了,如果我另外还有一个方法,也是需要这个规范呢?

function getUserInfo(user:{name:string,age:number}){
    console.log(`${user.name}${user.age}`)
}
function getInfo(user:{name:string,age:number}){
    console.log(`${user.name}${user.age}`)
}
getUserInfo({name:"TypeScript",age:28})
getInfo({name:"iamswr",age:28})

可以看出,函数getUserInfogetInfo都遵循同一个规范,那么我们有办法对这个规范复用吗?

// 首先把需要复用的规范,写到接口中 关键字 interface
interface infoInterface{
    name:string,
    age:number
}
// 然后把这个接口,替换到我们需要复用的地方
function getUserInfo(user:infoInterface){
    console.log(`${user.name}${user.age}`)
}
function getInfo(user:infoInterface){
    console.log(`${user.name}${user.age}`)
}
getUserInfo({name:"TypeScript",age:28})
getInfo({name:"iamswr",age:28})

那么有些参数可传可不传,该怎么处理呢?

interface infoInterface{
    name:string,
    age:number,
    city?:string // 该参数为可选参数
}
function getUserInfo(user:infoInterface){
    console.log(`${user.name}${user.age}${user.city}`)
}
function getInfo(user:infoInterface){
    console.log(`${user.name}${user.age}`)
}
getUserInfo({name:"TypeScript",age:28,city:"深圳"})
getInfo({name:"iamswr",age:28})

接口是如何规范函数的

// 对一个函数的参数和返回值进行规范
interface mytotal {
  // 左侧是函数的参数,右侧是函数的返回类型
  (a:number,b:number) : number
}

let total:mytotal = function (a:number,b:number):number {
  return a + b
}

console.log(total(10,20))

接口是如何规范数组的

interface userInterface {
  // index为数组的索引,类型是number
  // 右边是数组里为字符串的数组成员
  [index: number]: string
}
let arr: userInterface = ['TypeScript', 'iamswr'];
console.log(arr);

接口是如何规范类的

这个比较重要,因为写react的时候会经常使用到类

// 首先实现一个接口
interface Animal{
    // 这个类必须有name
    name:string,
    // 这个类必须有eat方法
    // 规定eat方法的参数类型以及返回值类型
    eat(any:string):void
}
// 关键字 implements 实现
// 因为接口是抽象的,需要通过子类去实现它
class Person implements Animal{
    name:string
    constructor(name:string){
        this.name = name
    }
    eat(any:string):void{
        console.log(`吃${any}`)
    }
}

那么如果想遵循多个接口呢?

interface Animal{
    name:string,
    eat(any:string):void
}
// 新增一个接口
interface Animal2{
    sleep():void
}
// 可以在implements后面通过逗号添加,和java是一样的
// 一个类只能继承一个父类,但是却能遵循多个接口
class Person implements Animal,Animal2{
    name:string
    constructor(name:string){
        this.name = name
    }
    eat(any:string):void{
        console.log(`吃${any}`)
    }
    sleep(){
        console.log('睡觉')
    }
}

接口可以继承接口

interface Animal{
    name:string,
    eat(any:string):void
}
// 像类一样,通过extends继承
interface Animal2 extends Animal{
    sleep():void
}
// 因为Animal2类继承了Animal
// 所以这里遵循Animal2就相当于把Animal也继承了
class Person implements Animal2{
    name:string
    constructor(name:string){
        this.name = name
    }
    eat(any:string):void{
        console.log(`吃${any}`)
    }
    sleep(){
        console.log('睡觉')
    }
}

六、泛型

泛型可以支持不特定的数据类型,什么叫不特定呢?比如我们有一个方法,里面接收参数,但是参数类型我们是不知道,但是这个类型在方法里面很多地方会用到,参数和返回值要保持一致性

// 假设我们有一个需求,我们不知道函数接收什么类型的参数,也不知道返回值的类型
// 而我们又需要传进去的参数类型和返回值的类型保持一致,那么我们就需要用到泛型

// <T>的意思是泛型,即generic type
// 可以看出value的类型也为T,返回值的类型也为T
function deal<T>(value:T):T{
    return value
}
// 下面的<string>、<number>实际上用的时候再传给上面的<T>
console.log(deal<string>("TypeScript"))
console.log(deal<number>(28))

实际上,泛型用得还是比较少,主要是看类的泛型是如何使用的

class MyMath<T>{
  // 定义一个私有属性
  private arr:T[] = []
  // 规定传参类型
  add(value:T){
    this.arr.push(value)
  }
  // 规定返回值的类型
  max():T{
    return Math.max.apply(null,this.arr)
  }
}

// 这里规定了类型为number
// 相当于把T都替换成number
let mymath = new MyMath<number>()
mymath.add(1)
mymath.add(2)
mymath.add(3)
console.log(mymath.max())

// 假设我们传个字符串呢?
// 则会报错:类型“"TypeScript"”的参数不能赋给类型“number”的参数。
mymath.add("TypeScript")

那么我们会思考,有了接口为什么还需要抽象类?

接口里面只能放定义,抽象类里面可以放普通类、普通类的方法、定义抽象的东西。

比如说,我们父类有10个方法,其中9个是实现过的方法,有1个是抽象的方法,那么子类继承过来,只需要实现这一个抽象的方法就可以了,但是接口的话,则是全是抽象的,子类都要实现这些方法,简而言之,接口里面不可以放实现,而抽象类可以放实现。