# 一、基础知识

# 1、类

  在 JavaScript 学习的时候,我们就提到了:为了让其他语言的开发者更快的适应 js 语法,ES6 中提供了类的写法:

构造函数方式
function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.say = function () {
  console.log(this.name, this.age)
}

var obj = new Person('lencamo', 22)
obj.say()
class 类方式
class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  say() {
    console.log(this.name, this.age)
  }
}

var obj = new Person('lencamo', 22)
obj.say()

  现在我们在 typescript 中写一下:

class Person {
  // 在ts中,要对成员属性进行声明
  userName: string
  userAge: number
  // 或者定义一个初始化值
  // userName: ''
  // age = 0

  constructor(name: string, age: number) {
    this.userName = name
    this.userAge = age
  }

  say() {
    console.log(this.userName, this.userAge)
  }
}

var obj = new Person('lencamo', 22)
obj.say()
参数属性 ✍(成员属性声明简写)

相当于有前提的语法糖

class Person {
  // 在ts中,要对成员属性进行声明
  constructor(public name: string,public age: number) {
    this.name = name
    this.age = age
  }

  say() {
    console.log(this.name, this.name)
  }
}

var obj = new Person('lencamo', 22)
obj.say()

  更多的,学过 java 的同学应该对下面的修饰符内容并不陌生:

下面这些修饰符,像 java 一样,可以直接加在类方法和类属性前面

readonly 只读的
public 公开可访问的
protected 当前类/子类可访问的
private 当前类可访问的

  下面就结合类的继承写一个案例:

class Person {
  protected userName: string

  constructor(name: string) {
    this.userName = name
  }
}

class Children extends Person {
  // constructor(userName: string) {
  //   super(userName)
  // }

  run() {
    return this.userName
  }
}

var obj = new Children('lencamo')
console.log(obj.run())
console.log(obj.userName) // 报错

# 2、访问器

让私有属性能在外界访问

class Person {
  private _name: string
  private _age: number

  constructor(name: string, age: number) {
    this._name = name
    this._age = age
  }

  // getter/setter:对属性的访问进行拦截操作
  set age(newValue: number) {
    if (newValue >= 0 && newValue < 200) {
      this._age = newValue
    }
  }

  get age() {
    return this._age
  }
}

const p = new Person('lencamo', 21)
p.age = -10
console.log(p.age)

# 3、抽象类(abstract)

// 抽象方法必须出现在抽象类中
// 并且抽象类是不能被实例化的
abstract class Db {
  abstract connection(): void
  abstract auth(): void
}

// 子类可以继承并实现抽象类
class Mysql extends Db {
  connection() {
    //
  }
  auth() {
    //
  }
}
感受应用场景 ✍
  • 以前
class Rectangle {
  constructor(public width: number, public height: number) {}

  getArea() {
    return this.width * this.height
  }
}

class Circle {
  constructor(public radius: number) {}

  getArea() {
    return this.radius ** 2 * Math.PI
  }
}

// 通用函数(类型不明确)
function calcArea(shape: Rectangle | Circle) {
  return shape.getArea()
}

var area1 = calcArea(new Rectangle(10, 20))
var area2 = calcArea(new Circle(5))

console.log(area1)
console.log(area2)
  • 现在
abstract class Shape {
  // 待实现方法
  abstract getArea(): number
}

class Rectangle extends Shape {
  constructor(public width: number, public height: number) {
    super()
  }

  getArea() {
    return this.width * this.height
  }
}

class Circle extends Shape {
  constructor(public radius: number) {
    super()
  }

  getArea() {
    return this.radius ** 2 * Math.PI
  }
}

// 通用函数(鸭子类型)
function calcArea(shape: Shape) {
  return shape.getArea()
}

var area1 = calcArea(new Rectangle(10, 20))
var area2 = calcArea(new Circle(5))

console.log(area1)
console.log(area2)
拓展:鸭子类型

  Typescript 对应类型检测的时候使用的就是鸭子类型

描述:

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

特点:

只关心属性和行为,不关心具体是不是对应的类型

现象:

class Person {
  constructor(public name: string, public age: number) {}
}

class Dog {
  constructor(public name: string, public age: number) {}

  running() {}
  swing() {}
}

function printPerson(p: Person) {
  console.log(p.name, p.age)
}

// 下面类型检测不会报错?
printPerson(new Person('lencamo', 20))
printPerson(new Dog('狗狗', 5))
printPerson({ name: 'haha', age: 3 })

const person: Person = new Dog('狗狗', 5)

# 4、接口实现(implements)

  implements 的中文意思:实现,其作用是提前对 class 属性和方法进行约束

class 类实现某个接口

interface IPerson {
  star: string
  age: number

  sing: () => void
}

interface IRun {
  running: () => void
}

// 接口使用1:对象类型
const ldh: IPerson = {
  star: 'ldh',
  age: 38,

  sing: function () {
    //
  }
}

// 接口使用2:接口类实现
class Person implements IPerson, IRun {
  star: string
  age: number

  constructor(star: string, age: number) {
    this.star = star
    this.age = age
  }

  sing() {
    //
  }

  running() {
    //
  }
}

const p = new Person('zmg', 34)
console.log(p.star)
p.sing()
约束效果
interface InerOne {
  name: string
  age: number
}

// 报错
class Person implements InerOne {
  name: string
  // age: number
}

# 5、接口和抽象类区别

使用

  重点关注是要捕捉子类的通用特性,还是要描述某些类该具备的一些行为

  以上面的案例为例,我们可以这样理解:

  • 抽象类
abstract class Shape {
  // 待实现方法
  abstract getArea(): number
}

// Rectangle is a Shape
class Rectangle extends Shape {
  //
}
  • 接口
interface IPerson {
  star: string
  age: number

  sing: () => void
}

interface IRun {
  running: () => void
}

// Person has sing/running in (Iperson, IRun)
class Person implements IPerson, IRun {
  //
}

# 二、泛型编程

  • Type:T
  • key、value:KV
  • Element:E
  • Object:O

# 1、类型参数化

  在开始前,先思考一下下面的这个例子,看看有没有其他方式可以实现:

function demo(arg: number | string): number | string {
  return arg
}

demo(1)
demo('lencamo')
答案
function demo<T>(arg: T): T {
  return arg
}

demo<number>(1)
demo<string>('lencamo')

  看到答案后,我们可以明显的发现:

在定义函数的时候,我们并没有预选指定具体的类型,而是在使用的时候指定类型,其实这就是使用泛型的一大好处

  当然,使用泛型的范围还包括函数、接口、类等。此处除外,我们还可以同时使用多个泛型

function demo<T, U>(arg1: T, arg2: U) {
  //
}

// 完整写法
demo<number, string>(1, '2')
demo<number[], boolean>([1, 2, 3], true)

// 省略写法
demo(1, '2')
demo([1, 2, 3], true)

# 2、泛型接口

  同样,我们先来看看一个问题:

function demo<T>(arg: T): void {
  console.log(arg.length) // 报错:类型"T"上不存在属性"length"
}

demo<string>('lencamo')

  那如何解决呢?

答案 —— 泛型约束

通过 Ctrl + 鼠标右键,可以发现泛型 T 本身就是一个 interface

function demo<T extends string | number[]>(arg: T): void {
  console.log(arg.length)
}

demo<string>('lencamo')
demo<number[]>([1, 2, 3])

  相应的,我们还可以 extends 自定义的 interface:

interface ILen {
  length: number
}

function demo<T extends ILen>(arg: T): void {
  console.log(arg.length)
}

demo<number[]>([1, 2, 3])

  关于泛型接口的使用,我们往往是用在增强 interface 的类型灵活性上面:

interface IDemo<T> {
  id: T
  name: string
}

const lisi: IDemo<number> = {
  id: 5,
  name: 'lisi'
}

const wangwu: IDemo<string> = {
  id: '207st2474x023t3',
  name: 'wangwu'
}
拓展:默认值
interface IDemo<T = string> {
  id: T
  name: string
}

const zhangsan: IDemo = {
  id: '207st2474x023t3',
  name: 'lisi'
}

# 3、泛型类

class Person<T, U> {
  _name: T
  _age: U

  constructor(name: T, age: U) {
    this._name = name
    this._age = age
  }
}

let p = new Person<string, number>('lencamo', 20)

# 4、泛型约束 ✨

  在泛型接口中,已经简单的接触了泛型约束的使用,下面我们进一步学习:

  • keyof

类似于联合类型

interface IPerson {
  name: string
  age: number
}

type ITypes = keyof IPerson // 相当于 'name' | 'age'
使用示例
function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {
  return obj[key]
}

const info = {
  name: 'lencamo',
  age: 20,
  height: 180
}

const nameValue = getObjectProperty(info, 'name')
console.log(nameValue)

# 5、类型映射 ✨

  有时候,我们的一个类型需要基于另外一个类型,但是我们又不想直接拷贝一份,此时我们就可以是映射类型

映射类型不能使用 interface 进行定义,在写法上我们可以与 😂 索引签名做一下比较。

interface Iperson {
  name: string
  age: number
}

// 类型映射
type MapType<T> = {
  [key in keyof T]: T[key]
}
type NewPerson = MapType<Iperson>

const ldh: NewPerson = {
  //
}
拓展:类型映射与修饰符
interface Iperson {
  name: string
  age?: number
  readonly height: number
  address?: string
}

type MapType<T> = {
  // 删除readonly,删除可选类型?
  -readonly [key in keyof T]-?: T[key]
}
type NewPerson = MapType<Iperson>

# 三、装饰器*

  所谓装饰器,通俗的讲就是锦上添花。举个例子:花就是装饰器,而锦就是类、属性、方法、参数等。

我们可以通过将附加的方式,向类、属性、方法、参数身上扩展类、属性、方法、参数等

  装饰器本质上是一个函数,它接受三个参数,分别是被装饰的目标、属性名称以及属性描述符。

  使用 ts 的装饰器时,要先在 tsconfig.json 中配置:

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

# 1、类装饰器

  类装饰器是不能传参的

function demo(target: any) {
  target.prototype.username = 'lencamo'
  target.age = 19
}

@demo
class Person {
  //
}
let p = new Person()
// @ts-ignore
console.log(p.username)
// @ts-ignore
console.log(Person.age)

  那如果我们硬要传参呢?

可以使用装饰器工厂

function demo(options: any) {
  return function (target: any) {
    target.prototype.username = options.name
    target.age = 19
  }
}

@demo({ name: 'lencamo' })
class Person {
  //
}
let p = new Person()

// @ts-ignore
console.log(p.username)
// @ts-ignore
console.log(Person.age)

# 2、属性装饰器

参考:

https://www.tslang.cn/docs/handbook/decorators.html

# 3、方法装饰器

参考:

https://www.tslang.cn/docs/handbook/decorators.html

更新于 : 7/8/2024, 10:21:14 AM