TypeScriptのClassまとめ

## Classの定義
```ts:class.ts
class Person {
    name: string;
    constructor(initName: string) {
        this.name = initName;
    }
}
const quill =  new Person('Quill');
console.log(quill);
```
```ts:出力結果
$ node dist/class.js
Person { name: 'Quill' }
```

コンパイルされるjsファイルを確認(es6)
jsではクラスにプロパティを定義するフィールドはない。
```ts:class.js(es6)
"use strict";
class Person {
    constructor(initName) {
        this.name = initName;
    }
}
const quill = new Person('Quill');
console.log(quill);
```
es5でコンパイルするとクラスの記述がなくなり、代わりにコンストラクタ関数で定義されている。
[【JS入門】コンストラクタ関数について](https://tsudoi.org/weblog/4929/)
```ts:class.js(es5)
"use strict";
var Person = /** @class */ (function () {
    function Person(initName) {
        this.name = initName;
    }
    return Person;
}());
var quill = new Person('Quill');
console.log(quill);

```

## メソッドの定義
```ts
class Person {
    name: string;
    constructor(initName: string) {
        this.name = initName;
    }
    greeting() {
        console.log(`hello ${this.name}`);
    }
}

const quill =  new Person('Quill');
quill.greeting();
```
```ts:出力結果
hello Quill
```

### メソッドをオブジェクトに代入して実行する場合
```ts
class Person {
    name: string;
    constructor(initName: string) {
        this.name = initName;
    }
    greeting() {
        console.log(`hello ${this.name}`);
    }
}

const quill =  new Person('Quill');
quill.greeting();

const anotherQuill = {
    anotherGreeting: quill.greeting
}
anotherQuill.anotherGreeting();
```
```ts:出力結果
hello Quill
hello undefined
```

undefinedになるのは、anotherQuilの中で定義している時点でgreeting()のthisはanotherQuilを指しているから。anotherQuilオブジェクトにはnameプロパティがない。
よって以下のようにプロパティを定義してやれば出力される。

thisは呼び出されたときに何を参照するか決まるので注意。

```ts
class Person {
    name: string;
    constructor(initName: string) {
        this.name = initName;
    }
    greeting() {
        console.log(`hello ${this.name}`);
    }
}

const quill =  new Person('Quill');
quill.greeting();

const anotherQuill = {
    name: 'anotherQuill',
    anotherGreeting: quill.greeting
}
anotherQuill.anotherGreeting();
```
```ts:出力結果
$ node dist/class.js
hello Quill
hello anotherQuill
```

### メソッドを実行する前にundefinedが出力されるのを回避する方法
上記の場合、undefinedが出力されることは実行結果を確認するまでわからない。
実行前に確認するためにはメソッドの引数を以下のように定義する。

```ts
class Person {
    name: string;
    constructor(initName: string) {
        this.name = initName;
    }
    greeting(this: { name: string }) { // ここ追記
        console.log(`hello ${this.name}`);
    }
}

const quill =  new Person('Quill');
quill.greeting();

const anotherQuill = {
    anotherGreeting: quill.greeting
}
anotherQuill.anotherGreeting();
```
greetingを実行する場合オブジェクトのnameプロパティを参照すると明記することで、
vscode上でanotherGreeting()を実行する前にエラー表示を確認することができる。(一番下の行)
![スクリーンショット 2022-04-19 14.17.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637441/01ecfd4d-a7b2-b6cf-74ce-091a383f5430.png)

## クラスを型として定義する
上でgreeting()の引数としていたthisの書き方を下記のようにできる。
このようにすることでクラスを型として定義することができる。
この場合、anotherQuillにはgreeting()とnameプロパティを定義しなければならない。
```ts
class Person {
    name: string;
    constructor(initName: string) {
        this.name = initName;
    }
    greeting(this: Person) {
        console.log(`hello ${this.name}`);
    }
}

const quill =  new Person('Quill');
quill.greeting();

const anotherQuill = {
    greeting: quill.greeting,
    name: 'anotherQuill'
}
anotherQuill.greeting();
```

## public修飾子とprivate修飾子
```ts
class Person {
    public name: string;
    private age: number;
    constructor(initName: string, initAge: number) {
        this.name = initName;
        this.age  = initAge;
    }
    greeting(this: Person) {
        console.log(`hello ${this.name}. I'm ${this.age}`);
    }
}

const quill =  new Person('Quill', 24);
quill.greeting();
```
何も指定しないとpublicになる。
privateにすると読み込みも書き込みもできない。

## 初期化の省略
constructor()に修飾子を付けて引数を定義すると省略することができる。
```ts
class Person {
    constructor(public name: string, private age: number) {
    }
    greeting(this: Person) {
        console.log(`hello ${this.name}. I'm ${this.age}`);
    }
}

const quill =  new Person('Quill', 24);
quill.greeting();
```

## readonly
readonlyを指定するとクラス内外から書き込みすることができない。
ただしconstructorでは書き換えることができる。
```ts
class Person {
    constructor(public readonly name: string, private age: number) {
        this.name = 'readonly'
    }
    greeting(this: Person) {
        console.log(`hello ${this.name}. I'm ${this.age}`);
    }
}
```

## 継承とprotected修飾子
protectedを指定することで継承した子クラスからプロパティにアクセスできる。
```ts
class Person {
    constructor(public readonly name: string, protected age: number) {
        this.name = 'readonly'
    }
    greeting(this: Person): void {
        console.log(`hello ${this.name}. I'm ${this.age}`);
    }
}

class Teacher extends Person {
    constructor(name: string, age: number, public subject: string) {
        super(name, age);
    }
    greeting(): void {
        console.log(`hello ${this.name}. I'm ${this.age}. subject is ${this.subject}`);
    }
}
```

## ゲッターとセッター
```ts
class Person {
    constructor(public readonly name: string, protected age: number) {
        this.name = 'readonly'
    }
    greeting(this: Person): void {
        console.log(`hello ${this.name}. I'm ${this.age}`);
    }
}

class Teacher extends Person {
    get subject() {
        if(!this._subject) {
            throw new Error("no subject");
        }
        return this._subject;
    }
    set subject(value) {
        if(!value) {
            throw new Error("no subject");
        }
        this._subject = value;
    }
    constructor(name: string, age: number, private _subject: string) {
        super(name, age);
    }
    greeting(): void {
        console.log(`hello ${this.name}. I'm ${this.age}. subject is ${this.subject}`);
    }
}

const teacher = new Teacher('Quill', 38, 'Math')
teacher.subject = 'music!!';
console.log(teacher.subject);
```
```ts:出力結果
$ node dist/class.js
music!!
```

## static
```ts
class Person {
    static species = 'homo sapiens';
    static isAdult(age: number) {
        if(age > 17) return true;
        return false;
    }
    constructor(public readonly name: string, protected age: number) {
        this.name = 'readonly'
    }
    greeting(this: Person): void {
        console.log(`hello ${this.name}. I'm ${this.age}`);
    }
}
console.log(Person.species);
```
```ts:出力結果
$ node dist/class.js
homo sapiens
```

## abstractクラス
abstractクラスはインスタンスを生成できない。
```ts
abstract class Person {
    static species = 'homo sapiens';
    static isAdult(age: number) {
        if(age > 17) return true;
        return false;
    }
    constructor(public readonly name: string, protected age: number) {
        this.name = 'readonly'
    }
    greeting(this: Person): void {
        console.log(`hello ${this.name}. I'm ${this.age}`);
        this.explainJob();
    }
    abstract explainJob(): void;
}

class Teacher extends Person {
    explainJob(): void {
        console.log(`I am a teacher and I teach ${this.subject}`);
    }
    get subject() {
        if(!this._subject) {
            throw new Error("no subject");
        }
        return this._subject;
    }
    set subject(value) {
        if(!value) {
            throw new Error("no subject");
        }
        this._subject = value;
    }
    constructor(name: string, age: number, private _subject: string) {
        super(name, age);
    }
}

const teacher = new Teacher('Mike', 39, 'Moth');
teacher.greeting();
```
```ts:出力結果
$ node dist/class.js
hello readonly. I'm 39
I am a teacher and I teach Moth
```