--- title: TypeScript Class tags: TypeScript 讀書會 --- # Class ## Generic Classes Class 和 interface 一樣可使用泛型: ```typescript= class Box<Type> { contents: Type; constructor(value: Type) { this.contents = value; } } const b = new Box('Hello!'); ``` ### Type Parameters in Static Members 因為在 runtime 的時候,`Box.defaultValue` 只會有一個值,他也許會在執行時被設定為 string 或是 number,不像是 instance 每次都是 new 出新的,member 間不會互相影響,因此不能在 static 的 member 使用泛型。 ```typescript= class Box<Type> { static defaultValue: Type; } Box.defaultValue = 's'; const a = Box.defaultValue; Box.defaultValue = 1; const b = Box.defaultValue; ``` ### this at Runtime in Classes TypeScript 不會改變 JavaScript 在 runtime 時的行為,因此 this 的值還是取決於是從哪裡呼叫方法的(ref:[淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂](https://blog.techbridge.cc/2019/02/23/javascript-this/))。 ### 箭頭函式 可以在 class 中利用箭頭函式的特性,確保 this 會被訪問到 instance。 ```typescript= class MyClass { name: string; constructor(name: string) { this.name = name; } getName = () => { return this.name; }; } const c = new MyClass('c'); const obj = { name: "obj", getName: c.getName, }; console.log(obj.getName()); // c ``` 1. 就算不使用 TypeScript,箭頭函式這特性在 JavaScript 也是可用的。 2. 會使用到更多的內存,因為每個 instance 裡都會建立一個該方法,就不是共用原型鏈中的方法: ```typescript= class MyClass { name: string; constructor(name: string) { this.name = name; } getName() { return this.name; }; } const a = new MyClass('a'); const a1 = new MyClass('a1'); console.log('MyClass', a.getName === a1.getName); // true class MyClass1 { name: string; constructor(name: string) { this.name = name; } getName = () => { return this.name; }; } const b = new MyClass1('a'); const b1 = new MyClass1('a1'); console.log('MyClass1', b.getName === b1.getName); // false ``` 3. 這樣就不能在子類別中用 super 呼叫箭頭函式了,因為無法從原型鏈中找到對應的方法。 ```typescript= class MyClass { name: string; constructor(name: string) { this.name = name; } getName = () => { return this.name; }; } class D extends MyClass { constructor(name: string) { super(name); } getNameD() { return super.getName(); } } const d = new D('a'); console.log(d.getNameD()); // error ``` ### this parameters 在 TypeScript 的 function 中如果定義 this 參數,那會在編譯成 JavaScript 的時候被移除: ```typescript= // TypeScript input function fn(this: SomeType, x: number) { /* ... */ } // JavaScript output function fn(x) { /* ... */ } ``` 如果是在 class 裡面的 methods 內寫參數 this 的話,TypeScript 會去檢查當下使用的 this 是不是符合參數型別: ```typescript= class MyClass { name = "MyClass"; getName(this: MyClass) { return this.name; } } const c = new MyClass(); // OK c.getName(); const obj = { name: 'obj', getName: c.getName, } // OK obj.getName(); // Error, would crash const g = c.getName; console.log(g()); ``` 1. 避免使用 this 的時候出錯 2. 所有 instance 共用 class 的 methods,而不是 instance 各有一個 3. 就可以使用 super 呼叫了 ### this Types 在 class 裡面的 this 會動態變成 new 的 class: ```typescript= class Box { contents: string = ""; set(value: string) { this.contents = value; return this; } } const b = new Box(); // a1: Box const a1 = b.set('string'); class ClearableBox extends Box { clear() { this.contents = ''; } } const cb = new ClearableBox(); // a2: ClearableBox const a2 = cb.set('string'); ``` 如果拿 this 當作 class 內 method 參數的型別設置的話,那 TypeScript 會去判斷使用該 method 的是否與 new 的 class 相同: ```typescript= class Box { content: string = ""; sameAs(other: this) { return other.content === this.content; } } class DerivedBox extends Box { otherContent: string = "?"; } const base = new Box(); const derived = new DerivedBox(); const anotherDerived = new DerivedBox(); // OK derived.sameAs(derived); // OK derived.sameAs(anotherDerived); // error derived.sameAs(base); ``` ### this-based type guards this 也以接在回傳值後面和 is 搭配使用,這麼做可以使通過 if 判斷內的該物件變成 is 後指定的型別([文件的程式碼範例](https://www.typescriptlang.org/play?strictPropertyInitialization=false#code/MYGwhgzhAEBiCWICmBlAnhALkgtgeQCMArJYTaAbwFgAoaaeCBZACgEoAuaTAC0YZjMkAJSQAHSrXr0ATkkwBXGQDtufGPGVYwy4EgD2AMziIR4gNxToAXyuMAIvDll9MtOy69+-R88yu0STppOUUVNW8tTB09I2hfUn83S2DbYMYAOXkAd1cAayQAEw8IjRgszFyZAsLoADJSoOloUKVVLwgAOmUc-KKU+jT6YH0omQUXGRYxBQIQeGBoMTBeLiwZTQBzABoljYA3FaRoHsq+wq4CfX1kHTZKNLTaUEhBU1EJJAAPbGVCt+Q6CwuEIJDITWgIzGEyS0xWPDWmA2yh2S1m80WUN+mERyM292owXoEAUYiQU2WvF2hjAIAgSDYAxstCeNBeUHiTkSAWg31+-xMgIw2HwxESEOAfBAhTkyi4QiBItBiQA2gBdFKszTYGQ0vTQCpVGoQnj6LC4raa2jPUZYaCGCD6eWmRUgsXggC8JyQ2UFZjELAARIZrgB6AhgGSdTA-QO7YPXQOM600eDGFgO-SdRhCdgEqyZzpYpDKTCa3l045p6AZx3ZiAJSbuNj54KFyWIGUl8tISsMdOFzK9apFPMQwumrCaoA)) ### Parameter Properties 在 constructor 的參數前也可以直接指定 [Member Visibility](https://www.typescriptlang.org/docs/handbook/2/classes.html#member-visibility),這樣就會直接被轉換成該 class 的 properties(前一章用 member 但這裡用 properties,不曉得建出來的東西是不是有不同)了,這被稱作 Parameter Properties: ```typescript= class Params { constructor( public readonly x: number, protected y: number, private z: number ) { // No body necessary } } const a = new Params(1, 2, 3); console.log(a.x); // Error console.log(a.z); ``` ### Class Expressions Class expressions 可以將 class 指定給某個變數,差別在於 class expressions 不需要再宣告 class 命名: ```typescript= const Member = class { constructor(public name: string) { this.name = name; } } const m = new Member('s'); ``` ### abstract Classes and Members TypeScrpt 的 class 可能會是抽象的,那些抽象的 field 或 method 可以使用 abstract class 宣告,並且透過衍生類別實作定義在 abstract class 的方法: ```typescript= // abstract class abstract class Base { abstract getName(): string; printName() { console.log("Hello, " + this.getName()); } } // derived class class Derived extends Base { getName() { return "world"; } } const d = new Derived(); d.printName(); ``` 但是要注意的是 abstract class 不能直接 new instance,另外如果 derived class 沒有實作 abstract class 要求的 field 或 methods 也不行: ```typescript= // Error const b = new Base(); // Error class Derived extends Base {} ``` ### Abstract Construct Signatures 有時候會想要在一個函式裡透過參數來產生某個 abstract class 的 derived class 的 instance,但這麼寫是不行的: ```typescript= function greet(ctor: typeof Base) { // Error const instance = new ctor(); instance.printName(); } ``` 像上方那種寫法會導致 TypeScript 認為你在 new 一個 abstract,要改成像這樣子: ```typescript= function greet(ctor: new () => Base) { const instance = new ctor(); instance.printName(); } // OK greet(Derived); // error greet(Base); ``` ### Relationships Between Classes 在 TypeScript 中是裡用結構來比較兩個 class 是否相等、可互相交替使用,其中也包含是否有最低程度的 field 及 methods 的子集合: ```typescript= class Point1 { x = 0; y = 0; } class Point2 { x = 0; y = 0; } // 兩者結構相同 OK const p: Point1 = new Point2(); class X { x = 0; } // Point2 的結構滿足 X 所需的 OK const x: X = new Point2(); ``` 以下是極端可以,但不要這麼做的狀況: ```typescript= class Empty {} function fn(x: Empty) { } // 不管是什麼 object 都符合空的 Empty class 需要的一切 fn(window); fn({}); fn(fn); ```