【JavaScript】クラス③ 〜クラスフィールド〜

# 概要
JavaScriptを学習、理解を深めるため「[JavaScript Primer 迷わないための入門書](https://jsprimer.net/)」を読み、
理解した内容等を記載していく。

「[【JavaScript】JavaScript入門一覧](https://qiita.com/andota05/items/22fae452225b785fd405)」に他の記事をまとめています。

# この記事で理解できること
* クラスフィールド構文について
* Publicクラスフィールド、Privateクラスフィールド、静的クラスフィールド
* 静的メソッド

# クラスフィールド構文(ES2022〜)
* クラスのインスタンスが持つ`プロパティ`、またはその`初期化をわかりやすく宣言的にすることができる`構文。
* Publicクラスフィールド、Privateクラスフィールド、静的クラスフィールドが存在する。

```js
// クラスフィールドの構文
class クラス {
    プロパティ名 = プロパティの初期値; // クラスフィールド    
}
```

# 【Tips】 クラスフィールドとコンストラクタの初期化併用時
* クラスフィールドでの初期値設定とコンストラクタでの初期化の併用が可能。
* 同じプロパティ名への定義がある場合は、`constructorメソッド内での定義`でプロパティは上書きされる。
* 初期化が行われる順番として「**クラスフィールドの初期化**」後、「**コンストラクタの初期化**」が行われる。

```js
class Human {
  // ①クラスフィールドから初期化され
  name = "名無しさん";
  
  // ②その後、コンストラクタで初期化(ここでは引数で受け取ったnameを設定)
  constructor(name) {
    console.log(this.name); // => 名無しさん(このタイミングではクラスフィールドの初期値)
    this.name = name;
  }
}

const playerA = new Human("taro");
// コンストラクタの初期化で上書きされていることが分かる
console.log(playerA.name) // => taro
```

# Publicクラスフィールド
* クラスの外からアクセスできるプロパティ
* 前項の例では「name」プロパティが該当する。

```js
class Human {
  // Publicクラスフィールド
  name = "名無しさん";
  
  constructor(name) {
    this.name = name;
  }
}

const playerA = new Human("taro");
console.log(playerA.name) // => taro(「コンソールに出力する」というクラス外の処理から参照できている)
```

# Privateクラスフィールド
* 外からアクセスされたくないインスタンスのプロパティを定義する際に使用する。
* `#`をフィールド名の前につけたクラスフィールドを定義する。
* クラスを定義する時点でクラスに必要な`すべてのPrivateクラスフィールド`を定義しておく必要がある。

```js
class Person {
  name = "名無しさん";
  #age = 0;
  
  constructor(name, age) {
    this.name = name;
    this.#age = age;
    // 未定義のPrivateクラスフィールドのプロパティを後から追加できない
    // this.#hobby = "野球"; // => SyntaxError: Private field '#hobby' must be declared in an enclosing class
  }
  
  // Privateクラスフィールドageの値を返すメソッド
  personAge() {
    console.log(this.#age);
  }
}

const personA = new Person("Taro", 23);

// Publicクラスフィールドは参照できる
console.log(personA.name); // => Taro

// Privateクラスフィールドは参照できない
// console.log(personA.#age); // => SyntaxError: Private field '#age' must be declared in an enclosing class

// クラス内(の処理等)からはPrivateクラスフィールドにアクセスできる
personA.personAge(); // => 23
```
# 静的クラスフィールド
* クラスから`インスタンス化せず`に参照可能なプロパティのこと。
* `static`キーワードをクラスフィールド名の前に付ける。
* Privateな静的クラスフィールドも定義できる。

```js
class Animal {
  static DOG = "犬";
  static CAT = "猫";
  static #BIRD = "鳥";
  LION = "ライオン";
  
  // クラス内(の処理)からは参照できる
  static bird() {
    return this.#BIRD;
  }
}

console.log(Animal.DOG);      // => 犬
console.log(Animal.CAT);      // => 猫
// console.log(Animal.#BIRD); // => SyntaxError: Private field '#BIRD' must be declared in an enclosing class
console.log(Animal.bird());   // => 鳥
console.log(Animal.LION);     // => undefined(Publicクラスフィールドも非インスタンス化では参照できない)
``` 

# 静的メソッド
* クラスを`インスタンス化せずに`利用できるメソッド。
* `static`キーワードをメソッド名の前に付ける。
* 静的メソッドにおける`this`はインスタンスではなく、クラス自身を参照する。
* そのため静的メソッドは、クラスの`インスタンスを作成する処理`や`クラスに関係する処理`を書くために利用される。

```js
class Person {
  name;

  constructor(name) {
    this.name = name;
  }

  static greeting(type) {
    if (type === "morning") {
      console.log("Good Morning World!!");
    }
    
    if (type === "hello") {
      console.log("Hello World!!");      
    }
  }  
  
  static callName() {
    console.log(this);
  }
  
  static newCreateInstance(name) {
    return new this(name);
    //     new Person(name)と意味的に同じ
  }
}

// インスタンス化せずに呼び出せている
Person.greeting("morning");   // => Good Morning World!!

// thisはクラス自身を参照する
Person.callName();            // => [class Person]

// インスタンス化を行う静的メソッド
const personA = Person.newCreateInstance("taro");
console.log(personA.name);    // => taro
```

# 【Tips】 クラスフィールドを使ってプロパティの存在を宣言する
* クラスフィールドを定義することで、クラスが持つプロパティを明示できる。
* クラスフィールドでは、プロパティの初期値は省略可能。
* 省略した場合、`undefined`で初期化される。

```js
// 「Humanクラスはnameプロパティを持っているよ」と明示できる
class Human {
  name; // 初期値を省略した場合、undefinedで初期化される
}

const playerA = new Human();
console.log(playerA.name) // => undefined
```
```js
// クラスフィールドを使用しない場合
class Human {
  // constructorメソッドをわざわざ書かないといけない
  constructor() {
    this.name;
  }
}
```

# クラスフィールドでのthisと、アロー関数の組み合わせ
* クラスフィールドには関数(式)も定義できる(`ES2015`の構文を使用できる環境であればアロー関数で定義するのがオススメ)
* アロー関数内での`this`は常に外側のスコープを参照する性質がある。([アロー関数とthis](https://qiita.com/andota05/items/1ae332a252e838b85572#:~:text=%E3%81%95%E3%82%93%27%2C%20%27Boby%E3%81%95%E3%82%93%27%20%5D-,%E3%82%A2%E3%83%AD%E3%83%BC%E9%96%A2%E6%95%B0%E3%81%A8this,-%E3%82%A2%E3%83%AD%E3%83%BC%E9%96%A2%E6%95%B0%E3%81%A7))
* アロー関数の性質を利用することで確実に`クラスのインスタンス`を参照することが可能。
* また、クラスフィールドで定義したメソッドは、クラスの`インスタンス`に対して`メソッドを定義`することになる。
* 参照:『[プロトタイプメソッドとインスタンスメソッドの違い](https://qiita.com/andota05/items/764e6f160665fffe599d#%E3%83%97%E3%83%AD%E3%83%88%E3%82%BF%E3%82%A4%E3%83%97%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%A8%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E9%81%95%E3%81%84)』

```js
class Person {
  name; // <-greetingメソッドは各インスタンスのnameの値を参照する

  // Publicクラスフィールドgreetingメソッドをアロー関数で定義する
  greeting = () => {
    console.log(`${this.name}さん、こんにちは`);
  }
    
  constructor(name) {
    this.name = name;
  }
}

const personA = new Person("Taro");
personA.greeting(); // => Taroさん、こんにちは
```