# TypeScript Class ( 類別 )

類(Class)是一種支持**物件導向**程式設計的核心結構。
它允許開發者創建具有特定屬性和方法的**藍圖**或**模板**。
**可被繼承**
```
class Person {
name: string;
age: number;
gender: string;
height: number;
weight: number;
}
// 使用new 把 Person 的類別實體出來
const leo = new Person();
leo.name = "Leo 李歐";
leo.age = 18;
leo.gender = '鋼鐵直男';
leo.height = 179;
leo.weight = 50;
```
透過直接賦予上去,跟類別 Person根本上還是不一樣的東西
```
const leo: Person = {
name:"Leo 李歐",
age:18,
gender:'鋼鐵直男',
height:179,
weight:50,
}
console.log(leo instanceof Person); // false
```
## 常用寫法
_ => 慣用寫法,通常代表私有的。例如 _ name
不加_的 => 別人就可以直接使用,例如: leo.name 來取資料
### 正常寫法
```
class Person {
// 創建和初始化在類或子類中創建的對象
constructor(
private _name: string ,
private _age: number ,
private _gender: string ,
private _height: number ,
private _weight: number ,
){}
// get 取得。可以宣告 回傳的函式型別,不可帶入參數
get name():number {
return this._name;
}
// set 設置。不可宣告 回傳的函式型別 會報錯
set name(name:string){
this._name=name;
}
// ...
get bmi(){
return this._weight / Math.pow(this._height / 100,2);
}
get profile(){
return `${this._name}(${this._age}歲)`
}
// ...
}
const leo = new Person('Leo 里歐',18,'男',179,50)
consoel.log(leo.profile); // Leo 里歐 (18歲)
consoel.log(leo.bmi()); // 25.7 之類
```
### 一般寫法
```
class Person {
name: string = '';
age: number = 0;
gender: string = '';
height: number = 0;
weight: number = 0;
// 創建和初始化在類或子類中創建的對象
constructor(
_name: string ,
_age: number ,
_gender: string ,
_height: number ,
_weight: number ,
){
this.name = _name;
this.age = _age;
this.gender = _gender;
this.height = _height;
this.weight = _weight;
}
}
const leo = new Person('Leo 里歐',18,'男',179,50)
```
#### 縮寫法 =>
```
class Person {
constructor(
public name: string,
public age: number,
public gender: string,
public height: number,
public weight: number,
){}
}
const leo = new Person('Leo 里歐',18,'男',179,50)
```
## 繼承extends && super
```
class Person {
constructor(
protected name: string,
protected age: number,
protected gender: string,
protected height: number,
protected weight: number,
){}
}
class Taiwanese extends Person {
private readonly _from:string = 'Taiwan' // readOnly 只能在建構子中初始化
constructor(
name: string,
age: number,
gender: string,
height: number,
weight: number,
){
super(_name,_age,_gender,_height,_weight);
}
greet():void {
console.log(`Hi , I'm ${this._name}. I come from ${this._from}`)
}
}
const leo = new Taiwanese('Leo 里歐',18,'男',179,50);
leo.greet(); // Hi , I'm 里歐. I come from Taiwan
```
### super 在構造函數中的使用
當子類繼承自父類時,子類的構造函數必須調用 super(),這樣才能正確初始化父類的部分。這通常是在子類構造函數的第一行進行。
```
class Parent {
constructor(protected name: string) {
console.log(`Parent constructor: ${this.name}`);
}
}
class Child extends Parent {
constructor(name: string, private age: number) {
super(name); // 調用父類的構造函數
console.log(`Child constructor: ${this.name}, Age: ${this.age}`);
}
}
let child = new Child("John", 30);
```
在這個例子中,Child 類繼承了 Parent 類。子類的構造函數中使用 super(name) 調用了父類的構造函數,確保 name 被正確初始化
### super 在方法中的使用
super 也可以用來調用父類的方法,這尤其在覆蓋(Override)方法時很有用,當你想要擴展或修改父類方法的行為而不是完全替換它時。
這兩個例子展示了如何使用 super 在不完全重寫父類方法的情況下,擴展或修改其行為。這種方式允許子類建立在父類現有功能的基礎上,從而增加新的功能或修改行為,而不破壞或複製父類的原有實現。
#### 例子1
```
class Parent {
sayHello() {
console.log("Hello from Parent");
}
}
class Child extends Parent {
sayHello() {
super.sayHello(); // 調用父類的 sayHello 方法,插進去來
console.log("Hello from Child");
}
}
let child = new Child();
child.sayHello();
```
這裡,Child 類的 sayHello 方法首先調用 Parent 類的 sayHello 方法,然後添加它自己的行為。這種方式允許子類構建在父類功能之上,而不是從頭開始。
#### 例子2
假設有一個 DataProcessor 類,它具有一個處理數據的基本方法。你想創建一個子類來在處理前驗證數據,並在處理後檢查結果。
```
class DataProcessor {
processData(data: string) {
console.log(`Processing data: ${data}`);
// 假設處理數據的邏輯
return `Processed: ${data}`;
}
}
class ValidatingDataProcessor extends DataProcessor {
processData(data: string) {
if (!data) {
throw new Error("No data provided!");
}
console.log("Data validation passed.");
const result = super.processData(data); // 調用父類的 processData 方法
console.log(`Data processed with result: ${result}`);
return result;
}
}
const processor = new ValidatingDataProcessor();
processor.processData("Sample data.");
```
在這個例子中,ValidatingDataProcessor 覆蓋了 DataProcessor 的 processData 方法。在其自己的方法中,它首先檢查數據是否為空,然後記錄一條數據驗證通過的消息,接著調用父類的 processData 方法處理數據,最後輸出處理結果。
### super 訪問父類屬性
雖然在 TypeScript 中,super 主要用於調用父類的方法和構造函數,但它也可以用來訪問父類中的屬性,尤其是當這些屬性被子類的方法或構造函數訪問時。
```
class Parent {
protected value: number = 42;
}
class Child extends Parent {
displayValue() {
console.log(`Value: ${super.value}`); // 訪問父類的 protected 屬性
}
}
let child = new Child();
child.displayValue();
```
在這個例子中,Child 類通過 super.value 訪問了 Parent 類的 value 屬性。
## 運算子
### constructor (建構函數)
constructor 是一個特殊的方法,用於創建和初始化在類或子類中創建的對象。每個類只能有一個 constructor。如果未顯式定義,一個空的 constructor 將被默認添加。
```
class Person {
constructor(
public name: string,
public age: number
) {
// 初始化物件時,自動設定 name 和 age 屬性
}
}
```
在這個例子中,constructor 接受兩個參數並將它們設為公共屬性。
### public(公開修飾符)
1. public - 任何人都可以透過該類別的實體來存取該屬性的值或使用該函
式。(不宣告時預設為 public )
2. protected - 只有在該類別或其子類別裡才能存取該屬性的值或是使用該
函式。
3. private - 只有在該類別裡才能存取該屬性的值或使用該函式
在 TypeScript 中,public 是一個存取修飾符,用於設定類成員(屬性或方法)的可見性。public 成員可以在任何地方被自由訪問,是預設的存取級別。
```
class Animal {
public species: string;
constructor(species: string) {
this.species = species;
}
public display(): void {
console.log(`This animal is a ${this.species}.`);
}
}
let cat = new Animal("Cat");
cat.display(); // 輸出: This animal is a Cat.
```
這個 Animal 類定義了一個公開方法 display 和一個公開屬性 species。
### protected(受保護的修飾符)
protected 修飾符類似於 private,但它允許在子類中訪問父類中的受保護成員。
```
class Parent {
protected protectedMethod(): void {
console.log('Protected method');
}
}
class Child extends Parent {
public test(): void {
this.protectedMethod(); // 可以訪問受保護方法
}
}
let child = new Child();
child.test(); // 正常運作
// child.protectedMethod(); // 錯誤: 'protectedMethod' 是受保護的。
```
### private(私有修飾符)封裝概念
private 修飾符用於限制類成員(屬性或方法)的訪問範圍。當一個成員被標記為 private 時,它只能在定義它的類內部被訪問。
```
class Example {
private secretMethod(): void {
console.log('This is private.');
}
public show(): void {
this.secretMethod();
}
}
let example = new Example();
example.show(); // 可以間接訪問 private 方法
// example.secretMethod(); // 錯誤: 'secretMethod' 是私有的,只能在類 'Example' 內部訪問。
```
### instanceof (實例運算子)
instanceof 運算子用於測試一個物件是否為某個類的實例,或者說這個物件是否使用這個類或其子類之一構造。
```
class Car {
constructor(
public brand: string
) {}
}
let myCar = new Car("Toyota");
console.log(myCar instanceof Car); // 輸出: true
console.log(myCar instanceof Animal); // 輸出: false
```
在這個例子中,myCar 是 Car 類的實例,所以 myCar instanceof Car 返回 true,而 myCar instanceof Animal 則返回 false,因為它不是 Animal 類的實例。
### static(靜態修飾符)
static 修飾符用於定義類的靜態成員,這些成員可以不實例化類就可以訪問。靜態成員屬於類本身,而不是任何類的實例。
```
class StaticExample {
static staticProperty = "Static property";
static staticMethod(): void {
console.log('This is a static method.');
}
}
console.log(StaticExample.staticProperty); // 直接訪問靜態屬性
StaticExample.staticMethod(); // 直接呼叫靜態方法
```
### abstract(抽象類和方法)
抽象類是供其他類繼承的基類,不能直接被實例化。抽象類中可以包含抽象方法,這些方法必須在派生類中被實現。
```
abstract class AbstractClass {
abstract abstractMethod(): void;
}
class ConcreteClass extends AbstractClass {
abstractMethod(): void {
console.log('Implemented abstract method');
}
}
let concrete = new ConcreteClass();
concrete.abstractMethod(); // 實現了抽象方法
// let abstract = new AbstractClass(); // 錯誤: 不能創建抽象類的實例
```
### Override(覆蓋)
「覆蓋」(Override)是一個核心概念,尤其在使用如 TypeScript 這樣的語言時更是如此。覆蓋發生於子類中,當子類重新定義繼承自父類的方法時,這個重新定義的方法將會替換原有的方法實現。
即使沒有使用 override 關鍵字,子類中的方法仍然可以覆蓋(override)父類中的方法。override 關鍵字不是必需的。
TypeScript 4.3 版本引入了一個新的 override 關鍵字,用於明確標記子類中應該覆蓋的方法。這有助於捕捉錯誤,比如當預期覆蓋父類的某個方法而父類中實際沒有該方法時,TypeScript 編譯器會報錯。
#### 為何使用 Override
* 增強或改變功能:子類可以擴展或修改繼承的方法功能,使其更適合子類的需要。
* 多態性:通過覆蓋,相同的方法名在不同的類中可以有不同的實現,這是多態性的一種表現,允許在運行時根據對象的實際類型調用相應的方法。
#### 示例
```
class BaseClass {
greet() {
console.log("Hello from BaseClass");
}
}
class DerivedClass extends BaseClass {
override greet() {
console.log("Hello from DerivedClass");
}
}
const base = new BaseClass();
base.greet(); // 輸出: Hello from BaseClass
const derived = new DerivedClass();
derived.greet(); // 輸出: Hello from DerivedClass
```
在這個例子中:
* BaseClass 定義了一個 greet 方法。
* DerivedClass 繼承自 BaseClass 並覆蓋了 greet 方法,使用了 override 關鍵字來明確表示這是一個覆蓋的行為。
* 當 DerivedClass 的實例調用 greet 方法時,它使用的是 DerivedClass 中定義的版本。
好處
使用 override 關鍵字的好處包括:
* 更清晰的意圖表達:代碼更易讀,因為其他開發者可以清楚地看到哪些方法是故意覆蓋的。
* 編譯時安全:如果父類中沒有要覆蓋的方法,或者父類方法被移除或改名,TypeScript 編譯器會立即報錯,這有助於提早捕捉到潛在的錯誤。