TypeScript 類(Class)

TypeScript 類(Class)

自 ES6 起,終於迎來了 class,對於開發者來說,終於可以使用基於類的面向對象式編程。TypeScript 在原 ES6 中類的基礎上,還添加了一些新的功能,比如幾種訪問修飾符,這是在其他面嚮對象語言中早就實現了的。

JavaScript 的類作爲語法糖,我們不但需要知道怎麼去使用,還應該瞭解其本質,涉及到原型的部分希望大家能深入理解。

1. 慕課解釋

類描述了所創建的對象共同的屬性和方法。通過 class 關鍵字聲明一個類,主要包含以下模塊:

  • 屬性
  • 構造函數
  • 方法

2. 類的本質

JavaScript 中,生成實例對象可以通過構造函數的方式:

案例演示

function Calculate (x, y) {
  this.x = x
  this.y = y
}

Calculate.prototype.add = function () {
  return this.x + this.y
}

var calculate = new Calculate(1, 2)
console.log(calculate.add()) // 3

運行案例點擊 "運行案例" 可查看在線運行效果

如果通過 class 關鍵字進行改寫:

案例演示

class Calculate {
  // 類的屬性
  public x: number
  public y: number

  // 構造函數
  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }

  // 類的方法
  add () {
    return this.x + this.y
  }
}

const calculate = new Calculate(1, 2)
console.log(calculate.add()) // 3

console.log(typeof Calculate) // 'function'
console.log(Calculate === Calculate.prototype.constructor) // true

運行案例點擊 "運行案例" 可查看在線運行效果

代碼解釋:

最後一行,可以看出,類指向其構造函數本身,class 關鍵字可以看做是一個語法糖。

constructor() 方法是類的默認方法,通過 new 來生成對象實例時,自動調用該方法。換句話說,constructor() 方法默認返回實例對象 this

3. 類的繼承

基於類的程序設計中一種最基本的模式是允許使用繼承來擴展現有的類,這樣可以抽出公共部分讓子類複用。

使用 extends 關鍵字來實現繼承:

案例演示

// 繼承 JavaScript 內置的 Date 對象
class LinDate extends Date {

  getFormattedDate() {
    var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
    return this.getDate() + "-" + months[this.getMonth()] + "-" + this.getFullYear();
  }
}

const date = new LinDate()

console.log(date.getFullYear());     // 2020
console.log(date.getFormattedDate()) // 7-Jan-2020

運行案例點擊 "運行案例" 可查看在線運行效果

代碼解釋: LinDate 繼承了 Date 的功能,可以使用父類 Date 的方法 getFullYear(),也可以使用自身的方法 getFormattedDate()

子類在 constructor 內中使用 super() 方法調用父類的構造函數,在一般方法內使用 super.method() 執行父類的方法。

案例演示

class Animal {
  public name:string

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

  move(distance: number = 0) {
      console.log(`${this.name} moved ${distance}m.`)
  }
}

class Dog extends Animal {
  constructor(name: string) { 
    // 調用父類的構造函數
    super(name)
  }

  move(distance = 10) {
      console.log('bark...')
      // 執行父類的方法
      super.move(distance) 
  }
}

const dog: Animal = new Dog('Coco')

dog.move() // Coco moved 10m.

運行案例點擊 "運行案例" 可查看在線運行效果

代碼解釋:

第 16 行,通過 super() 調用了父類的構造函數。

第 22 行,通過 super 關鍵字調用父類的方法。

4. 訪問修飾符

TypeScript 可以使用四種訪問修飾符 public、protected、private 和 readonly。

4.1 public

TypeScript 中,類的成員全部默認爲 public,當然你也可以顯式的將一個成員標記爲 public,標記爲 public 後,在程序類的外部可以訪問。

class Calculate {
  // 類的屬性
  public x: number
  public y: number

  // 構造函數
  public constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }

  public add () {
    return this.x + this.y
  }
}

4.2 protected

當成員被定義爲 protected 後,只能被類的內部以及類的子類訪問

class Base {
  protected baseUrl: string = 'http://api.com/'

  constructor() {}

  protected request(method: string) {
    const url = `${this.baseUrl}${method}`
    // TODO 封裝基礎的 http 請求
  }
}

class Address extends Base {
  get() {
    return this.request('address')
  }
}

代碼解釋:

第 2 行,Base 類的屬性 baseUrl 被定義爲受保護的,那麼第 7 行該屬性在類中被訪問是可以的。

第 14 行,因 Address 類是 Base 類的子類,在子類中允許訪問父類中被定義爲受保護類型的方法 request() 。

4.3 private

當類的成員被定義爲 private 後,只能被類的內部訪問

class Mom {
  private labour() {
    return 'baby is coming'
  }
}

class Son extends Mom {
  test () {
    this.labour() // Error, Property 'labour' is private and only accessible within class 'Mom'
  }
}

代碼解釋:

第 9 行,父類中的 labour() 方法被定義爲私有方法,只能在父類中被使用,子類中調用報錯。

4.4 readonly

通過 readonly 關鍵字將屬性設置爲只讀的。只讀屬性必須在聲明時或構造函數裏被初始化。

class Token {
  readonly secret: string = 'xjx*xh3GzW#3'

  readonly expired: number

  constructor (expired: number) {
    this.expired = expired
  } 
}

const token = new Token(60 * 60 * 24)
token.expired = 60 * 60 * 2 // Error, expired 是隻讀的

代碼解釋:

最後一行,因 Token 類的屬性 expired 被設置爲只讀屬性,不可被修改。

5. 靜態方法

通過 static 關鍵字來創建類的靜態成員,這些屬性存在於類本身上面而不是類的實例上

class User {
  static getInformation () {
    return 'This guy is too lazy to write anything.'
  }
}

User.getInformation() // OK

const user = new User()
user.getInformation() // Error 實例中無此方法

代碼解釋: getInformation() 方法被定義爲靜態方法,只存在於類本身上,類的實例無法訪問。

靜態方法調用同一個類中的其他靜態方法,可使用 this 關鍵字。

class StaticMethodCall {

  static staticMethod() {
      return 'Static method has been called'
  }
  static anotherStaticMethod() {
      return this.staticMethod() + ' from another static method'
  }

}

代碼解釋: 靜態方法中的 this 指向類本身,而靜態方法也存在於類本身,所以可以在靜態方法中用 this 訪問在同一類中的其他靜態方法。

非靜態方法中,不能直接使用 this 關鍵字來訪問靜態方法。而要用類本身或者構造函數的屬性來調用該方法:

class StaticMethodCall {
  constructor() {
      // 類本身調用
      console.log(StaticMethodCall.staticMethod())

      // 構造函數的屬性調用
      console.log(this.constructor.staticMethod())
  }
  static staticMethod() {
      return 'static method has been called.'
  }
}

代碼解釋: 類指向其構造函數本身,在非靜態方法中,this.constructor === StaticMethodCall 爲 true, 也就是說這兩種寫法等價。

6. 抽象類

抽象類做爲其它派生類的基類使用,它們一般不會直接被實例化,不同於接口,抽象類可以包含成員的實現細節。

abstract 關鍵字是用於定義抽象類和在抽象類內部定義抽象方法。

abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log('roaming the earch...');
    }
}

const animal = new Animal() // Error, 無法創建抽象類實例

通常我們需要創建子類繼承抽象類,將抽象類中的抽象方法一一實現,這樣在大型項目中可以很好的約束子類的實現。

class Dog extends Animal {
  makeSound() {
    console.log('bark bark bark...')
  }
}

const dog = new Dog()

dog.makeSound()  // bark bark bark...
dog.move()       // roaming the earch...

7. 把類當做接口使用

類也可以作爲接口來使用,這在項目中是很常見的。

class Pizza {
  constructor(public name: string, public toppings: string[]) {}
}

class PizzaMaker {
  // 把 Pizza 類當做接口
  static create(event: Pizza) {
    return new Pizza(event.name, event.toppings)
  }
}

const pizza = PizzaMaker.create({ 
  name: 'Cheese and nut pizza', 
  toppings: ['pasta', 'eggs', 'milk', 'cheese']
})

第 7 行,把 Pizza 類當做接口。

因爲接口和類都定義了對象的結構,在某些情況下可以互換使用。如果你需要創建一個可以自定義參數的實例,同時也可以進行類型檢查,把類當做接口使用不失爲一個很好的方法。

這就是 TypeScript 的強大功能,而且非常靈活,擁有全面的面向對象設計和通用的類型檢查

8. 小結

本節介紹了類的本質及其使用方法,需要注意:

  • 類指向其構造函數本身。
  • 靜態方法存在於類本身上面而不是類的實例上。
  • 抽象類中的抽象方法不包含具體實現並且必須在派生類中實現。
  • TypeScript 新增了 public、protected、private 等訪問修飾符。
  • 子類繼承父類時,在其構造函數 constructor() 中不要忘了 super() 方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章