---
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);
```