# TypeScript Class ( 類別 ) ![image](https://hackmd.io/_uploads/H1N77a2fC.png) 類(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 編譯器會立即報錯,這有助於提早捕捉到潛在的錯誤。