---
# System prepended metadata

title: TypeScript Class
tags: [TypeScript 讀書會]

---

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