iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
Modern Web

TypeScript 魔法 - 喚醒你的程式碼靈感系列 第 23

Day23 - 建立物件藍圖 - 類別(class)

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20231008/20152047YWryjPYPCe.png

什麼是 OOP?

OOP(Object-Oriented Programming,物件導向程式設計)是一種程式設計範式或方法論,以物件為核心,將資料和與資料相關的操作封裝在一起,以建立可重複使用和易於維護的程式碼。在物件導向程式設計中,程式被組織為一組相互關聯的物件,每個物件代表現實世界中的一個實體,具有屬性和方法。

什麼是類別?

類別是物件導向程式設計(Object-Oriented Programming,OOP)的核心概念之一,它提供了一種建立物件的藍圖。類別定義了物件的屬性和方法,允許我們將相關的程式碼組織在一起,並建立具有相似特性的多個物件。在 TypeScript 中,類別是一種強型別的結構,可以明確指定屬性和方法的型別。

如何定義一個類別

在 TypeScript 中,要定義一個類別,我們使用 class 關鍵字,後面跟著類別名稱。

class Department {
  // 屬性
  name: string;

  // 建構函式
  constructor(name: string) {
    this.name = name;
  }

  // 方法
  describe(this: Department) {
    console.log(`部門:${this.name}`);
  }
}

如何建立物件

要建立類別的物件,我們使用 new 關鍵字,後面跟著類別名稱,並傳遞建構函式所需的參數。

class Department {
  name: string;

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

  describe() {
    console.log(`部門:${this.name}`);
  }
}

const engineering = new Department('工程部');

建構函式和 this 關鍵字

建構函式是類別的一部分,它在建立物件時被呼叫,用於初始化物件的屬性。在建構函式內部,我們使用 this 關鍵字來存取物件的屬性,並將參數的值分配給物件的對應屬性。

class Department {
  name: string;

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

  describe() {
    console.log(`部門:${this.name}`);
  }
}

const engineering = new Department('工程部');
engineering.describe(); // 部門:工程部

關於 this 指向

當我們有一個 personCopy 的新物件,只有 sayHello 方法並指向原本的 perosn 物件,看看有什麼變化。

class Department {
  name: string;

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

  describe() {
    console.log(`部門:${this.name}`);
  }
}

const engineering = new Department('工程部');
engineering.describe(); // 部門:工程部

const engineeringCopy = {
  describe: engineering.describe,
};
engineeringCopy.describe(); // 部門:undefined

此時,TypeScript 編譯並不會報錯,但在運行時,我們可以看到輸出的值是 undefined,這是因為 engineeringCopy.describethis 是指向自己,它是一個偽物件,本身沒有 name 屬性,最後,我們得到的輸出是 undefined

函式帶入 this 做一層防護

describe 方法中,使用 this 參數指定該方法應該被調用在屬於 Department 類別的物件上。這樣一來,在方法內我們可以安全存取 this 物件的屬性。

class Department {
  name: string;

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

  describe(this: Department) {
    console.log(`部門:${this.name}`);
  }
}

const engineering = new Department('工程部');
engineering.describe(); // 部門:工程部

// 自己的物件新增一個 name 屬性,否則 TypeScript 會報錯
const engineeringCopy = {
  name: '會計部',
  describe: engineering.describe,
};
engineeringCopy.describe(); // 部門:會計部

public、private 和 protected

publicprivateprotected 是 TypeScript 特有的功能,用於控制類別的屬性和方法可存取性。

public

屬性或方法為是公有的,表示可以從任何地方被存取,預設所有的屬性和方法都是 public,可以省略。

class Department {
  name: string;
  employees: string[] = [];

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

  addEmployee(employee: string) {
    this.employees.push(employee);
  }

  printEmployeeInformation() {
    console.log(this.employees.length); // 2
    console.log(this.employees); // ['肉鬆', '傑尼龜']
  }
}

const engineering = new Department('工程部');

engineering.addEmployee('肉鬆'); // 可以存取
engineering.addEmployee('傑尼龜'); // 可以存取

engineering.printEmployeeInformation(); // 可以存取

private

屬性或方法是私有的,表示只能在類別內部存取,無法從類別外部存取。

class Department {
  name: string;
  private employees: string[] = [];

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

  addEmployee(employee: string) {
    this.employees.push(employee);
  }

  printEmployeeInformation() {
    console.log(this.employees.length);
    console.log(this.employees);
  }
}

const engineering = new Department('工程部');

engineering.addEmployee('肉鬆'); // 可以存取
engineering.addEmployee('傑尼龜'); // 可以存取

engineering.employees[2] = '小明'; // TypeScript 報錯,'employees' 是私用屬性,只可從類別 'Department' 中存取

engineering.printEmployeeInformation(); // 可以存取

protected

屬性或方法是受保護的,表示只能在類別內部和子類別存取,無法從類別外部存取。

class Department {
  name: string;
  protected employees: string[] = [];

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

  addEmployee(employee: string) {
    this.employees.push(employee);
  }

  printEmployeeInformation() {
    console.log(this.employees.length);
    console.log(this.employees);
  }
}

class RDDepartment extends Department {
  constructor(name: string) {
    super(name);
  }

  addRD(employee: string) {
    // 因為 employees 是 protected,所以可以在子類中存取
    this.employees.push(employee);
  }
}

const rd = new RDDepartment('工程部');
rd.addEmployee('肉鬆'); // 可以存取
rd.addRD('傑尼龜'); // 可以存取

rd.employees[2] = '小明'; // TypeScript 報錯,'employees' 是受保護屬性,只可從類別 'Department' 及其子類別中存取

rd.printEmployeeInformation(); // 可以存取

繼承(extends)

在 TypeScript 中,我們可以使用 extends 關鍵字實現繼承,這允許子類別繼承父類別的屬性和方法。子類別可以使用 super 關鍵字來調用父類別的建構函式或方法。

class Department {
  name: string;
  employees: string[] = [];

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

  addEmployee(employee: string) {
    this.employees.push(employee);
  }

  printEmployeeInformation() {
    console.log(this.employees.length);
    console.log(this.employees);
  }
}

class RDDepartment extends Department {
  constructor(name: string, employee: string[]) {
    super(name); // // 使用 super 調用父類別的建構函式
    this.employees = employee
  }

  addRD(employee: string) {
    this.employees.push(employee);
  }
}

const rd = new RDDepartment('工程部', ['肉鬆', '傑尼龜']);

rd.printEmployeeInformation();

存取器(get、set)

在 TypeScript 中,我們可以使用 getset 控制類別的屬性的讀取和賦值。

class Person {

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

  get name() {
    return '小明';
  }

  set name(value) {
    console.log('setter: ' + value);
  }
}

let person = new Person('肉鬆'); // setter: 肉鬆
person.name = '傑尼龜'; // setter: 傑尼龜
console.log(person.name); // 小明

靜態屬性和方法(static)

在 TypeScript 中,我們可以使用 static 關鍵字定義靜態屬性或方法。靜態屬性或方法不需要物件類別就可以存取屬性或方法。

靜態屬性

class Department {
  static year = 2023;

  constructor() {
    console.log(this.year); // TypeScript 報錯,屬性 'year' 不存在於類型 'Department' 上
    console.log(Department.year); // 2023
  }
}

console.log(Department.year); // 2023

靜態方法

class Department {
  static createEmployee(name: string) {
    return {
      name,
    };
  }
}

const employee = Department.createEmployee('肉鬆'); // 可以直接調用靜態方法
console.log(employee); // {name: '肉鬆'}

抽象屬性和方法(abstract)

在 TypeScript 中,我們可以使用 abstract 關鍵字定義抽象屬性和方法。抽象類別是一個不能被物件化的類別,通常用來定義一個共同的介面,而具體實現留給子類別。

abstract class Department {
  static year: number = 2023;

  constructor() {
    console.log(Department.year); // 2023
  }

  // 抽象類別中的抽象方法,需要在子類別中實現
  abstract getEmployeeInfo(name: string): void;
}

class RDDepartment extends Department {
  getEmployeeInfo(name: string) {
    return {
      name,
      yearJoined: Department.year,
    };
  }
}

class ITDepartment extends Department {
  getEmployeeInfo(name: string) {
    return {
      name,
      yearJoined: Department.year,
    };
  }
}

const rd = new RDDepartment();
console.log(rd.getEmployeeInfo('肉鬆')); // {name: '肉鬆', yearJoined: 2023}

const it = new ITDepartment();
console.log(it.getEmployeeInfo('傑尼龜')); // {name: '傑尼龜', yearJoined: 2023}

本日重點

  1. 使用 class 關鍵字來定義一個類別。
  2. 使用 constructor 方法來初始化類別的屬性,這是在建立類別自動調用的特殊方法。
  3. 使用 this 參數指定該方法應該被調用在屬於哪個類別的物件上
  4. public 表示可以從任何地方被存取、private 表示只能在類別內部存取,無法從類別外部存取、protected 表示只能在類別內部和子類別存取,無法從類別外部存取。
  5. 使用 extends 關鍵字實現繼承,子類別可以使用 super 關鍵字來調用父類別的建構函式或方法。
  6. 使用 getset 控制類別的屬性的讀取和賦值。
  7. 使用 static 關鍵字定義靜態屬性或方法,需要物件類別就可以存取屬性或方法。
  8. 使用 abstract 關鍵字定義抽象屬性和方法,具體需要在子類別實現。

參考


上一篇
Day22 - 確保資料完整性 - 空值合併運算子
下一篇
Day24 - 實作介面與類別
系列文
TypeScript 魔法 - 喚醒你的程式碼靈感30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言