五、函数/接口/类/泛型语法总结

TypeScript

# 函数类型

函数的输入和输出进行约束,及参数和返回值

# 函数声明

// 参数的类型和返回值的类型
function func1(a: number, b: number): string {
    return 'func1'
}
// 参数类型和个数是固定的,否则报错
func1(100, 200)
1
2
3
4
5
6

# 函数类型表达式

// 普通函数
const func4 = function (a: number, b: number): string {
    return 'func2'
}
// 使用箭头函数
const func5: (a: number, b:number) => string = function(a, b) {
    return 'func2'
}
1
2
3
4
5
6
7
8

# 可选参数

可选参数一定要在必选参数后面,放在函数最后。

// 可以在b后面添加问号表示可选,也可以直接设置默认值,也可以不用传
function func2(a: number, b: number = 10, c?: number): string {
    return 'func1'
}

func1(100)
1
2
3
4
5
6

# 任意个数的参数

使用ES6rest操作符

function func3(a: number, b: number = 10, ...rest: number[]): string {
    return 'func1'
}

func1(100,200,300,400)
1
2
3
4
5

# 接口(interface)

接口,是一种规范、契约,约定对象的结构。

接口是用来约束一个对象的结构,我们要使用这个接口,就要遵循其全部的约定。

接口最直观的体现就是对象应该有哪些成员以及成员的类型都是什么样的?

# 定义接口

// 定义一个接口,里面确定要有两个成员,且都是字符串类型
interface Post {
  title: string  // 结尾可以使用逗号分隔,也可以使用分号去分割,还可以省略
  content: string
}
1
2
3
4
5

# 使用接口

// 使用的时候声明参数是Post类型,里面使用的时候不担心没有值
function printPost (post: Post) {
  console.log(post.title)
  console.log(post.content)
}

// title和content任何一个没有传或者不是字符串都会报错
printPost({
  title: 'this is a title',
  content:'this is a content'
})
1
2
3
4
5
6
7
8
9
10
11

# 可选成员 & 只读成员 & 动态成员

  • 可选成员 : 定义接口的时候添加问号,传参的时候可有可无
interface Post {
  title: string
  content: string
  subtitle?: string // 可选成员,可有可无,string or undefined
}

// 下面不传subtitle不会报错
const hello: Post = {
	title: 'this is a title',
    content:'this is a content'
}
1
2
3
4
5
6
7
8
9
10
11
  • 只读成员 :定义接口的时候前面添加readonly关键词,一经定义不能修改
interface Post {
  title: string
  content: string
  subtitle?: string
  readonly summary: string //只读成员,一经定义不能更改
}

const hello: Post = {
	title: 'this is a title',
    content:'this is a content',
    summary: 'this is a summary'
}

hello.summary = 'hello' // 报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 动态成员 :不确定有哪些成员,自己定义添加,一般这种都存在动态对象里面,例如程序中的缓存对象。

因为不知道有哪些成员名称,所以Cache里面使用[],指定键prop的类型是string,值的类型是number

interface Cache {
  [prop: string] : number
}

const cache: Cache = {}

cache['hello'] = 1
cache['hi'] = 2
1
2
3
4
5
6
7
8

#

类用来描述一类具体事物的抽象特征。TypeScript增强了class的相关语法,访问修饰符以及抽象类的概念等...

下面看一下TypeScript新增的内容:

# 需要对类的属性与方法进行声明

目的是为了给属性和方法做类型标注

class Person {
  // 需要对类的属性进行声明,可以添加默认值,也可以不添加
  // 两者有一个没有写,都会报错
  name: string = 'init name'
  age: number
  
  constructor (name: string, age: number) {
    // 如果不加声明,这里直接使用会报错,因为在TypeScipt需要明确属性,而不是动态添加
    this.name = name
    this.age = age
  }
  
  // 方法这些和之前是一样的,也要添加类型注解
  sayHi (msg: string): void {
    console.log(`I am ${this.name}, ${msg}`)
  }

  run (): void {
    this.sayHi('I am happy!')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 类成员访问修饰符(public/private/protected)

- public private protected
内部访问
外部访问 不可 不可
子类访问 不可

# 1. 定义一个构造函数

class Person {
  // 默认是public,加不加效果一样,建议去加
  public name: string = 'init name'
  // age属性是个私有属性,私有属性可以在函数内部通过this.age去访问
  private age: number
  // 受保护的,外界成员不可访问,子类成员可以访问
  protected gender: boolean

  constructor (name: string, age: number) {
    this.name = name
    this.age = age
    this.gender = true
  }

  sayHi (msg: string): void {
    console.log(`I am ${this.name}, ${msg}`)
    console.log(this.age) //自己内部访问私有属性是没有问题的
    console.log(this.gender) //自己内部访问受保护属性是没有问题的
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 2. 初始化实例对象并访问构造函数成员

const xm = new Person('xm', 18)
console.log(xm.name)
console.log(xm.age) // 报错,Property 'age' is private and only accessible within class 'Person'
console.log(xm.gender) // 报错,Property 'gender' is protected and only accessible within class 'Person' and its subclasses.
1
2
3
4

# 3. 创建子类继承构造函数并访问其成员

//定义一个Student类继承Person
class Student extends Person {
  constructor(name: string, age: number) {
    super(name, age)
    console.log(this.gender)
    console.log(this.age) // 报错,私有成员不能访问
    console.log(this.name)
  }
}
1
2
3
4
5
6
7
8
9

# 类的构造函数被私有化

  • private: 如果类的构造函数被私有化,那么不能被实例化和继承,这个时候只能在这个类的内部添加一个静态方法,通过静态方法添加实例。
  • protected: 如果类的构造函数被受保护,那么不能实例化,但是可以继承。
class Student extends Person {
  private constructor(name: string, age: number) {
    super(name, age)
    console.log(this.gender)
    console.log(this.name)
  }
  // 可以定义一个方法内部实例化
  static create (name: string, age: number) {
    return new Student(name, age)
  }
}

const xm = Student.create('xm', 18)
console.log(xm.name)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 类的只读属性

在属性前添加修饰符readonly,如果有访问修饰符,那么就跟在修饰符的后面.只读属性必须在声明时或构造函数里被初始化。

class Person {
  public name: string = 'init name'
  private age: number
  // 如果有访问修饰符,那么就跟在修饰符的后面
  protected readonly gender: boolean

  constructor (name: string, age: number) {
    this.name = name
    this.age = age
    this.gender = true
  }

  sayHi (msg: string): void {
    console.log(`I am ${this.name}, ${msg}`)
    console.log(this.gender = false) // Cannot assign to 'gender' because it is a read-only property.
  }

  run (): void {
    this.sayHi('I am happy!')
  }
}

let xm = new Person('xm', 18)
xm.gender = 'false' // 报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 类与接口

类与类之间的公共特征一般会用接口去抽象

比如下面两个不同的类,但是都有eatrun两个相同的方法,可以用接口约束两个类中公共的部分

class Person {
  eat (food: string): void {
    console.log(`优雅进餐:${food}`)
  }

  run (distance: number) {
    console.log(`直立行走:${distance}`)
  }
}

class Animal {
  eat (food: string): void {
    console.log(`不优雅进餐:${food}`)
  }

  run(distance: number) {
    console.log(`爬行:${distance}`)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 定义接口

// 可以定义一个接口实现一个能力,然后让一个类实现多个接口

interface Eat {
  eat (food: string): void
}

interface Run {
  run (distance: number): void
}
1
2
3
4
5
6
7
8
9

# 实现接口

// Person和Animal要实现接口,如果里面少了接口对应的方法,就会报错。
class Person implements Eat, Run{
  eat (food: string): void {
    console.log(`优雅进餐:${food}`)
  }

  run (distance: number) {
    console.log(`直立行走:${distance}`)
  }
}

class Animal implements Eat, Run{
  eat (food: string): void {
    console.log(`不优雅进餐:${food}`)
  }

  run(distance: number) {
    console.log(`爬行:${distance}`)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 抽象类

  • 抽象类与接口有些类似,也是约束子类中必须要有哪些成员,不同的是抽象类里面可以包含一些具体的实现
  • 抽象类只能继承,不能实例化对象
  • 抽象类中可以定义一些抽象方法
  • 抽象方法不需要方法体,当父类中有抽象方法的时候,子类必须要实现抽象方法

# 抽象类定义

// 添加abstract关键词之后就成为了抽象类
abstract class Animal {
  eat (food: string): void {
    console.log(`不优雅进餐:${food}`)
  }
  // 抽象类中可以定义一些抽象方法,也需要关键词abstract
  abstract run (distance: number): void
}
1
2
3
4
5
6
7
8

# 子类继承

class Dog extends Animal {
  // 可以在VSCode环境点击Dog使用快速修复自动生成代码实现
  // 这里实现了抽象类中的run抽象方法
  run(distance: number): void {
    console.log('爬行', distance)
  }
}

// 子类实例化
const d = new Dog()
d.eat('粮食')
d.run(100)
1
2
3
4
5
6
7
8
9
10
11
12

# 泛型

我们在定义函数、接口或者类的时候没有去指定类型,只有当使用的时候才去指定类型的一种特征。

其目的 就是为了极大程度复用我们的代码

举个例子:

下面是传入长度和值,返回一个数组

// 参数长度是number类型,value是number类型,返回的是number类型的数组
function createArray (length: number, value: number): number[] {
  const arr = Array<number>(length).fill(value)
  return arr
}

// 下面传入参数可以获得三个值为100的数字类型的数组
const res = createArray(3, 100) // res => [100, 100, 100]
1
2
3
4
5
6
7
8

上面的代码有个缺陷是只能返回数字类型的数组,如果换成其他类型就会报错,如何进行修改?

# 定义泛型参数

  • 函数名后面使用尖括号,里面定义泛型参数
  • 一般泛型参数都用大写的T作为名称,函数中不明确的类型都用T去代表
function createArray <T> (length: number, value: T): T[] {
  const arr = Array<T>(length).fill(value)
  return arr
}
1
2
3
4

# 调用时传入泛型参数的类型

  • 调用时在函数名后面用尖括号内部填入参数的类型
// 下面可以填充字符串类型或者数字类型都可以
const res = createArray<string>(3, 'foo')
const res1 = createArray<number>(3, 100)
1
2
3

总结 就是泛型参数将定义时不能明确的参数用一个T来代替,使用的时候指定T的类型

更新时间: 2021-09-15 12:03