切換語言為:簡體

詳解 TypeScript 中的類

  • 爱糖宝
  • 2024-10-09
  • 2034
  • 0
  • 0

在 TypeScript (TS) 中,類是物件導向程式設計的核心概念之一。類是用來建立物件的模板,它封裝了物件的狀態(屬性)和行為(方法)。TypeScript 提供了對類的全面支援,並且還增加了型別檢查功能,使得程式碼更加嚴謹和易於維護。

類的本質

類是物件導向程式設計(OOP)中的核心概念,它提供了建立物件的模板。在 TypeScript 中,類是用來封裝資料(屬性)和操作這些資料的方法的。

class Person {
  name: string; // 定義屬性
  age: number;

  // 建構函式,用於初始化類的屬性
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // 定義方法
  greet() {
    console.log(`你好,我的名字是 ${this.name},我今年 ${this.age} 歲了。`);
  }
}

// 例項化類
const person1 = new Person("Alice", 30);
person1.greet(); // 輸出: 你好,我的名字是 Alice,我今年 30 歲了。

在上面的程式碼中有如下解釋:

  1. 屬性:name 和 age 是類的屬性,它們儲存了每個例項的狀態(資訊)。

  2. 建構函式:constructor 是類的特殊方法,用於在建立物件時初始化屬性。

  3. 方法:greet() 是類的方法,用於表示物件的行為。

最終結果如下圖所示:

詳解 TypeScript 中的類

靜態成員與非靜態成員的區別

在 TypeScript 中,類的成員可以分為靜態成員和非靜態成員。它們的主要區別在於:

  1. 非靜態成員:屬於每個例項物件。每建立一個新的物件,都會建立一組獨立的非靜態成員。

  2. 靜態成員:屬於類本身,而不是例項。靜態成員可以透過類名直接訪問,不需要例項化物件。

如下程式碼所示:

class Car {
  static totalCars = 0; // 靜態變數,屬於類本身
  mileage: number; // 非靜態變數,屬於每個物件

  constructor(mileage: number) {
    this.mileage = mileage;
    Car.totalCars++; // 更新靜態變數
  }

  // 靜態方法
  static showTotalCars() {
    console.log(`總共有 ${Car.totalCars} 輛車。`);
  }

  // 非靜態方法
  showMileage() {
    console.log(`這輛車的里程是 ${this.mileage} 公里。`);
  }
}

const car1 = new Car(10000);
const car2 = new Car(20000);

Car.showTotalCars(); // 輸出: 總共有 2 輛車。
car1.showMileage(); // 輸出: 這輛車的里程是 10000 公里。

在上面的程式碼中有如下解釋:

  1. 靜態變數 totalCars 是一個類級別的屬性,所有 Car 物件共享這個值。

  2. 非靜態變數 mileage 是每個 Car 物件獨立的屬性,建立一個新物件就會有一個獨立的 mileage 值。

  3. 靜態方法 showTotalCars 是透過類名呼叫的,而不是透過物件呼叫的。

最終輸出結果如下圖所示:

詳解 TypeScript 中的類

為什麼靜態成員不能訪問非靜態成員?

由於靜態成員屬於類本身,而非靜態成員屬於例項。靜態成員和非靜態成員的生命週期不同。靜態成員在類載入時已經存在,而非靜態成員依賴於具體的例項物件。

class Example {
  static staticMember = "靜態成員";
  nonStaticMember = "非靜態成員";

  static staticMethod() {
    console.log(this.staticMember); // 這是正確的,因為 staticMember 是靜態的
    // console.log(this.nonStaticMember);  // 錯誤!靜態方法無法訪問非靜態成員
  }
}

Example.staticMethod(); // 輸出: 靜態成員

靜態方法和屬性只與類關聯,而非靜態屬性和方法與物件例項關聯。靜態方法中沒有 this 指向具體的例項,因此無法訪問屬於某個物件的非靜態屬性或方法。透過類直接呼叫靜態方法時,類內部沒有任何關於例項的上下文。

如何在靜態方法中訪問非靜態成員?

如果要在靜態方法中訪問非靜態成員,可以透過將例項物件作為引數傳遞給靜態方法,進而訪問非靜態成員。

class Car {
  mileage: number; // 非靜態成員
  static totalCars = 0; // 靜態成員

  constructor(mileage: number) {
    this.mileage = mileage;
    Car.totalCars++; // 統計建立的車輛數
  }

  static showCarMileage(car: Car) {
    console.log(`這輛車的里程是 ${car.mileage} 公里。`); // 透過傳遞的例項物件訪問非靜態成員
  }
}

const car1 = new Car(15000);
Car.showCarMileage(car1); // 輸出: 這輛車的里程是 15000 公里。

訪問修飾符(public, private, protected)

TypeScript 中的訪問修飾符用於控制類成員的訪問許可權:

  1. public:公有成員,可以在類的外部訪問(預設修飾符)。

  2. private:私有成員,只能在類的內部訪問,不能在外部或者子類中訪問。

  3. protected:受保護的成員,可以在類的內部和子類中訪問,但不能在類的外部訪問。

class Person {
  public name: string; // 公有屬性
  private age: number; // 私有屬性
  protected id: number; // 受保護屬性

  constructor(name: string, age: number, id: number) {
    this.name = name;
    this.age = age;
    this.id = id;
  }

  public greet() {
    console.log(`你好,我的名字是 ${this.name}`);
  }

  private showAge() {
    console.log(`我的年齡是 ${this.age}`);
  }

  protected showId() {
    console.log(`我的 ID 是 ${this.id}`);
  }
}

const person1 = new Person("Alice", 30, 123);
// person1.age;  // 錯誤,age 是私有屬性,無法在外部訪問
person1.greet(); // 輸出: 你好,我的名字是 Alice

訪問修飾符用於控制類的外部對類內部實現的訪問,從而實現資訊隱藏,這有助於保護類的內部狀態免於隨意修改。繼承中,protected 成員可以在子類中訪問,從而在繼承鏈中共享部分邏輯,而 private 成員則完全封閉。

繼承與方法重寫

TypeScript 支援類的繼承,透過 extends 關鍵字可以繼承父類的屬性和方法。繼承允許子類擴充套件父類的功能,並且可以重寫父類的方法。

class Animal {
  name: string;

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

  public makeSound(): void {
    console.log(`${this.name} makes a sound.`);
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name); // 呼叫父類建構函式
  }

  // 重寫父類的方法
  public makeSound(): void {
    console.log(`${this.name} barks.`);
  }
}

const dog = new Dog("Buddy");
dog.makeSound(); // 輸出: Buddy barks.

在子類的建構函式中,super 用於呼叫父類的建構函式,從而初始化父類的屬性。子類還可以重寫父類的方法來改變行為。

抽象類和抽象方法

抽象類是不能被例項化的類,它們通常用作基類,定義子類必須實現的抽象方法。抽象方法在抽象類中沒有具體實現,必須由子類提供具體實現。

abstract class Animal {
  abstract makeSound(): void; // 抽象方法

  public move(): void {
    console.log("The animal moves.");
  }
}

class Dog extends Animal {
  public makeSound(): void {
    console.log("Woof! Woof!");
  }
}

const dog = new Dog();
dog.makeSound(); // 輸出: Woof! Woof!
dog.move(); // 輸出: The animal moves.

Animal 是抽象類,不能直接例項化。抽象類用於提供通用功能,併爲子類定義行爲規範。makeSound 是抽象方法,沒有實現,子類必須提供具體實現。

介面(Interfaces)

介面定義了一組類必須實現的規範。介面只定義方法的簽名,而不提供方法的具體實現。

class Person {
  private _name: string;

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

  get name(): string {
    return this._name;
  }

  set name(newName: string) {
    if (newName.length > 0) {
      this._name = newName;
    } else {
      console.log("Name cannot be empty.");
    }
  }
}

const person = new Person("Alice");
console.log(person.name); // 獲取 name,輸出: Alice
person.name = "Bob"; // 設定 name
console.log(person.name); // 輸出: Bob

Drivable 是介面,定義了 drive 方法的簽名。Car 類透過 implements 實現了 Drivable 介面,並提供了 drive 方法的具體實現。

抽象類 vs 介面

抽象類和介面的基本概念:

  1. 抽象類:

    • 抽象類是不能被直接例項化的類,只能作為基類被繼承。

    • 抽象類可以包含抽象方法和具體方法。抽象方法沒有實現,必須由子類實現;具體方法可以在抽象類中有實現,子類可以繼承這些方法。

    • 抽象類用於定義通用的行爲規範,同時可以提供部分實現,適合用於構建類的層次結構。

  2. 介面

    • 介面是用來定義類的行爲規範的,它只包含方法和屬性的簽名,不包含具體實現。

    • 類可以透過 implements 關鍵字實現介面,必須提供介面中定義的所有方法和屬性的實現。

    • 介面主要用於型別檢查,並且允許一個類實現多個介面,實現類似於多繼承的效果。

抽象類可以包含建構函式、欄位(屬性)、具體方法、抽象方法,並且抽象方法必須由子類實現,具體方法可以由子類繼承和使用。

abstract class Animal {
  protected name: string;

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

  // 抽象方法,沒有實現
  abstract makeSound(): void;

  // 具體方法,有實現
  public move(): void {
    console.log(`${this.name} 正在移動。`);
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name);
  }

  public makeSound(): void {
    console.log(`${this.name} 汪汪叫!`);
  }
}

const dog = new Dog("小黑");
dog.makeSound(); // 輸出: 小黑 汪汪叫!
dog.move(); // 輸出: 小黑 正在移動。

介面只能定義方法和屬性的簽名,不包含任何實現,而且一個類可以實現多個介面,每個介面可以定義類的不同方面的行為。

interface Drivable {
  drive(): void;
}

interface Flyable {
  fly(): void;
}

class Plane implements Drivable, Flyable {
  public drive(): void {
    console.log("飛機在滑行。");
  }

  public fly(): void {
    console.log("飛機在飛行。");
  }
}

const plane = new Plane();
plane.drive(); // 輸出: 飛機在滑行。
plane.fly(); // 輸出: 飛機在飛行。

抽象類可以包含訪問修飾符(public、protected、private),用於控制成員的可見性和訪問許可權。而介面中的成員預設都是 public,不能使用 private 或 protected。

// 抽象類示例
abstract class Animal {
  protected name: string;

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

  abstract makeSound(): void;

  protected sleep(): void {
    console.log(`${this.name} 正在睡覺。`);
  }
}

class Cat extends Animal {
  constructor(name: string) {
    super(name);
  }

  public makeSound(): void {
    console.log(`${this.name} 喵喵叫!`);
  }

  public rest(): void {
    this.sleep(); // 可以訪問受保護的成員
  }
}

const cat = new Cat("小花");
cat.makeSound(); // 輸出: 小花 喵喵叫!
cat.rest(); // 輸出: 小花 正在睡覺。

// 介面示例
interface Swimmable {
  swim(): void;
}

// 不能在介面中使用訪問修飾符
interface Flyable {
  fly(): void;
}

class Fish implements Swimmable {
  public swim(): void {
    console.log("魚在游泳。");
  }
}

抽象類可以包含欄位(屬性)定義,並且可以有預設值。而介面不能包含欄位,只能定義方法簽名和屬性型別。

// 抽象類可以包含屬性
abstract class Vehicle {
  protected speed: number = 0;

  constructor(speed: number) {
    this.speed = speed;
  }

  abstract accelerate(amount: number): void;
}

class Car extends Vehicle {
  public accelerate(amount: number): void {
    this.speed += amount;
    console.log(`汽車的速度增加到 ${this.speed} km/h。`);
  }
}

const car = new Car(50);
car.accelerate(20); // 輸出: 汽車的速度增加到 70 km/h。

// 介面不能包含屬性
interface Drivable {
  drive(): void;
}

因此,如果一個類中有一些通用方法,可以透過抽象類提供部分實現,子類繼承這些方法。介面適合用於定義一組行爲規範,確保實現該介面的類提供特定的功能。

在實際開發中,抽象類和介面可以結合使用,充分利用它們各自的優勢。

// 定義介面
interface Swimmable {
  swim(): void;
}

interface Flyable {
  fly(): void;
}

// 抽象類
abstract class Animal {
  protected name: string;

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

  abstract makeSound(): void;

  public move(): void {
    console.log(`${this.name} 正在移動。`);
  }
}

// 具體類實現多個介面並繼承抽象類
class Duck extends Animal implements Swimmable, Flyable {
  constructor(name: string) {
    super(name);
  }

  public makeSound(): void {
    console.log(`${this.name} 呱呱叫!`);
  }

  public swim(): void {
    console.log(`${this.name} 正在游泳。`);
  }

  public fly(): void {
    console.log(`${this.name} 正在飛行。`);
  }
}

const duck = new Duck("小鴨子");
duck.makeSound(); // 輸出: 小鴨子 呱呱叫!
duck.move(); // 輸出: 小鴨子 正在移動。
duck.swim(); // 輸出: 小鴨子 正在游泳。
duck.fly(); // 輸出: 小鴨子 正在飛行。

Duck 類繼承了 Animal 抽象類,並實現了 Swimmable 和 Flyable 介面。抽象類提供通用的行為(如 move),而介面提供具體的行爲規範(如 swim 和 fly),使得程式碼設計更加靈活和清晰。

總結

TypeScript 的類是物件導向程式設計的核心,封裝了物件的狀態(屬性)和行為(方法),支援靜態成員和非靜態成員的區別。類可以使用訪問修飾符(publicprivateprotected)控制成員的可見性,並支援繼承和方法重寫來擴充套件父類的功能。抽象類和介面定義了行爲規範,其中抽象類可提供部分實現,而介面則用於型別約定,允許一個類實現多個介面,實現靈活的程式碼設計。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.