TS 中的类(面向对象)

基本用法

ts 中类的写法与 js 中基本一致,区别暂时理解为只是在 constructor 函数前,需要先定义这个类的公共属性,声明类型。并且在严格模式下属性都需要初始化。

可以是直接赋值,此时类型可以通过推导得到:

typescript 复制代码
class Person {
  name = 'Jay'
  age = 20
}

或通过 constructor 赋值:

typescript 复制代码
class Person {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

继承(extends)

继承多态 还有封装 属于面向对象的三大特性,且继承为后面介绍的多态的前提。跟es6 里类的继承几乎一致,比如 Person 类继承了 Animal 类,那么我们就称 Person 类为派生类子类 ),Animal 类为基类超类 ,也可以称为父类)。

  • 类之间的继承关系使用的是 extends 关键词;
  • 子类如果定义了构造函数 constructor,必须调用super()(相当于执行了父类的构造函数),且需要写在前面,之后才能在子类的 constructor 中使用 this
  • 可以在子类中重写父类的方法。也可以通过 super 调用父类的方法,且位置随意。

代码例子:

typescript 复制代码
// 定义一个基类
class Animal {
  eyesNumber: number
  constructor(num: number) {
    this.eyesNumber = num
  }
  run(speed: string) {
    console.log(`跑得${speed}`)
  }
}

// 定义一个派生类
class Person extends Animal {
  name: string
  constructor(name: string, num: number = 2) {
    // 使用 super 调用父类的构造函数
    super(num)
    this.name = name
  }

  run() {
    console.log('我是子类的方法,可以写在 super 前面')
    // 调用父类中的方法
    super.run('快')
  }
}

const person = new Person('Jay')
console.log(person.eyesNumber)
console.log(person.name)
person.run()

打印结果如下:

多态

定义:父类型的引用指向了子类型的对象,不同类型的对象(有继承关系)针对相同的方法,有不同的表现。 如下例子中,先定义一个父类 Father 和两个子类SonDaughter

typescript 复制代码
// 定义父类
class Father {
  name: string
  constructor(n: string) {
    this.name = n
  }
  work(days: number = 5) {
    console.log(`${this.name}一周工作${days}天`)
  }
}

// 定义子类
class Son extends Father {
  constructor(n: string) {
    super(n)
  }
  work(days: number = 3) {
    console.log(`${this.name}是男孩子,一周需要工作${days}天`)
  }
}

// 定义子类
class Daughter extends Father {
  constructor(n: string) {
    super(n)
  }
  work(days: number = 1) {
    console.log(`${this.name}是女孩子,一周只需要工作${days}天`)
  }
}

将上述 3 个类实例化,因为 Son 和 Daughter 是继承自 Father 类的,所以我们可以将 Son 或 Daughter 的实例对象赋值给类型为 Father 的变量,然后分别调用 work 方法:

typescript 复制代码
const chaim: Father = new Father('Chaim')
chaim.work()
const jay: Father = new Son('Jay')
jay.work()
const linda: Father = new Daughter('Linda')
linda.work()

输出如下图:

发现同样是 work 方法,但因为 jay(或 linda) 虽然定义时类型是 Father,但本质上类型是 Son(或 Daughter),所以执行的是 Son(或 Daughter) 的 work,这就是多态。

另外,如果子类型没有增加其它方法, 也可以让子类型引用指向父类型的实例(因为实例都是只有 name 属性和 work 方法的对象):

typescript 复制代码
const jay: Son = new Father('Jay')
jay.work() // Jay一周工作5天

成员修饰符

主要是描述类中的成员(属性,构造函数,方法)的可访问性。

  • public 公共的

类中成员默认的修饰符,代表是公共的,任何位置都可以访问类中的成员:

typescript 复制代码
class Singer {
  public name: string
  public constructor(name: string) {
    this.name = name
  }
  public releaseAlbum() {
    console.log(`一个歌手一年发一张专辑不过分吧,啊?${this.name}`)
  }
}

const singer = new Singer('Jay')
console.log(singer.name) // Jay
singer.releaseAlbum() // 一个歌手一年发一张专辑不过分吧,啊?Jay
  • private 私有的

类中的成员如果使用 private 修饰,则外部无法访问与修改,包括子类。如果想在外部访问或修改私有属性,可以通过定义 getter 和 setter:

typescript 复制代码
class Singer {
  private _name: string // 私有属性的属性名按惯例以 _ 开头
  constructor(name: string) {
    this._name = name
  }
  get name() {
    return this._name // 内部可以访问私有属性
  }
  set name(value) {
    // 可以在这里做一些操作
    this._name = value
  }
}

const singer = new Singer('Jay')
console.log(singer.name) // Jay
singer.name = 'Chaim'
console.log(singer.name) // Chaim

如果 constructorprivate 修饰,new Singer() 就会报错。

  • protected 受保护的

类中的成员如果使用 protected 修饰,则外部无法访问,但是子类中是可以访问的

typescript 复制代码
class Singer {
  protected name: string
  constructor(name: string) {
    this.name = name
  }
}

class Rapper extends Singer {
  constructor(name: string) {
    super(name)
  }
  sing() {
    console.log(`${this.name} 唱了一首歌`)
  }
}

const singer = new Singer('Jay')
console.log(singer.name) // 编辑器提示错误,属性 "name" 受保护,只能在类 "Singer" 及其子类中访问
const rapper = new Rapper('Hey')
rapper.sing() // Hey 唱了一首歌
  • readonly 只读的

类中的成员如果使用 readonly 修饰,则该属性成员就不能在外部被修改了,在类的内部的一般方法里也不能修改,只能在类的构造函数中进行修改:

typescript 复制代码
class Singer {
  readonly name: string
  constructor(name: string) {
    this.name = name
  }
}
const singer = new Singer('Jay')

注意:如果一个只读属性的类型是一个类,那么这个属性本身的值不能修改,但是它的属性是可以修改的。比如下例中 friend 属性是不能修改的,但 friend.name 可以:

typescript 复制代码
class Singer {
  name: string
  readonly friend: Singer
  constructor(name: string, friend?: Singer) {
    this.name = name
    if (friend)
      this.friend = friend
  }
}
const singer = new Singer('Jay', new Singer('Eson'))
singer.friend = new Singer('Jon') // 报错:无法分配到 "friend" ,因为它是只读属性
singer.friend.name = 'Ella' // 可以修改

参数属性(Parame Properties)

ts 提供了一种语法糖写法,即以上这些修饰符,可以直接对构造函数中的参数进行修饰,此时对应的属性会自动赋值,并且不用再特意在一开始时罗列出来:

typescript 复制代码
class Singer {
  // name: string // 构造函数中用了修饰符,这里无需再写
  constructor(public name: string) {
    // this.name = name // 当 new Singer('Jay') 时会自动将 'Jay' 赋值给 name
  }
}

存取器

TypeScript 支持通过 getters/setters 来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。

typescript 复制代码
class PersonName {
  constructor(public firstName: string, public lastName: string) {
    this.firstName = firstName
    this.lastName = lastName
  }
  get fullName() {
    return this.firstName + this.lastName
  }
  set fullName(val) {
    const nameArr = val.split(' ')
    this.firstName = nameArr[0]
    this.lastName = nameArr[1]
  }
}

const personName: PersonName = new PersonName('Jay', 'Zhou') 
console.log(personName.fullName) // JayZhou
personName.fullName = 'Chaim HL'
console.log(personName.fullName) // ChaimHL

如果把 set 部分的代码注释掉,则 fullName 就是个只读的属性,personName.fullName = 'Chaim HL' 就会报错。

静态属性 static

类中的成员如果使用 static 修饰,则是静态属性或静态方法,只能通过 类名. 的方式调用,注意 static 不能像其他修饰符那样在构造函数的参数中使用

typescript 复制代码
class PersonName {
  static firstName: string = 'Jay'
}

console.log(PersonName.firstName) // Jay

静态方法只能使用静态属性或方法:

typescript 复制代码
class Test {
  static num = 0
  static fn() {
    Test.bar() // bar
    this.bar() // bar
    console.log(Test.num) // 0
    console.log(this.num) // 0

  }
  static bar() {
    console.log('bar')
  }
}

抽象类

abstract 关键字定义的类为抽象类,可以包含同样被 abstract 修饰的方法,也就是抽象方法(一般没有任何具体的实现),也可以包含抽象属性(很少定义抽象属性),和实例方法。抽象类不能被实例化,而是为了让子类进行实例化及实现内部的抽象方法。不同于接口,抽象类可以包含成员的实现细节。

typescript 复制代码
abstract class Animal {
  // abstract name: string = 'Animal' // 可以定义抽象属性,但一般很少这么做
  // abstract eat(): void {} // 会报错,因为抽象方法不能有具体实现
  abstract eat(): void // 抽象方法只能定义在抽象类中
  run() {
    console.log('这是实例方法')
  }
}

// const animal = new Animal() // 报错:无法创建抽象类的实例
class Person extends Animal {
  eat() {
    console.log('子类必须实现抽象父类的抽象方法')
  }
}

const person: Person = new Person()
person.eat() // 子类必须实现抽象父类的抽象方法
person.run() // 这是实例方法

类类型

下例中,定义了一个 Singer 类,和一个类型为 Singer 的对象 singersinger 的值不一定得是通过 new Singer 得到,也可以直接赋值一个对象,只要该对象里有 Singer 中定义的实例属性和方法即可:

typescript 复制代码
class Singer {
  name: string
  constructor(name: string) {
    this.name = name
  }
  sing() {
    console.log('是谁在唱歌')
  }
}
const singer: Singer = {
  name: 'Jay',
  sing() { }
}

注意,类是不存在下面这种写法的,类只是继承某个类或实现某些个接口:

鸭子类型(duck typing)

最后说说 ts 中类型检测的一个原则 ------ 鸭子类型,就是侧重于检查对象的属性和行为,当两个对象它们具有相同的属性和方法时,它们就会被认为属于相同的类,即"如果一只鸟走起来像鸭子,游起来像鸭子,叫起来像鸭子,那么它就是一只鸭子"。

typescript 复制代码
class Person {
  constructor(public name: string) { }
  eat() { }
}

class Singer {
  constructor(public name: string) { }
  eat() { }

}

const p: Singer = new Person('Jay')

因为 Singer 类的属性和方法 Person 类也都拥有,所以可以将 Person 的实例赋值给类型为 Singerp。但如果 Singer 类还有个 run 方法,那么赋值就会报错。

相关推荐
无咎.lsy1 分钟前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec9 分钟前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec12 分钟前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
豆豆1 小时前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建
看到请催我学习2 小时前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins35202 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky2 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~2 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n03 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html