JavaScriptのClassについて part3

# 初めに
今回は`class`の静的メソッドの書き方と使い分けをまとめていきたいと思います。

主な参考文章はこちらです。
[Static properties and methods - javascript.info](https://javascript.info/static-properties-methods)
[Public and private class fields - v8.dev](https://v8.dev/features/class-fields#simpler_subclassing)
[Public class fields - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields)
[Private class features - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields)

# Static properties and methods
[static - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static)
`static`とは`class`で静的プロパティやメソッドを定義するキーワードです。
`static`で扱われるメソッドの`this`は自分のいる`class field`に参照しています。
```jsx
class Person {
  static staticProperty = 'abc'
  static staticMethod() {
    console.log(this === Person)
  }
}
//
console.log(Person.staticProperty) // abc
Person.staticMethod() // true
```
これから`class`内部の`this`と、`static`メソッドへの応用などまとめてみたいと思います。

# `this` in static methods
まずは`this`の参照についてです。前のpart2で紹介したように、`class`の内部でもどこに置かれたかと、どのように書かれるかと、`this`の参照が変わってきます。
ここでは簡単な例で`this`の参照先を把握していきたいと思います。
```jsx
class Person {
  constructor() {
    this.constructorMethod = () => {
      console.log(this === Person)
    }
  }

  static staticMethod() {
    console.log(this === Person)
  }

  classMethod() {
    console.log(this === Person)
  }
}

// Person.constructorMethod()
// TypeError: Person.constructorMethod is not a function
Person.staticMethod()
// true
// Person.classMethod()
// TypeError: Person.classMethod is not a function
```
下のように`constructor()`と`class`フィールドでのメソッドはインスタンスで反映されますが、インスタンスから`staticMethod()`利用できません。
```jsx
let person = new Person()
person.constructorMethod() // false

// person.staticMethod()
// TypeError: person.staticMethod is not a function

person.classMethod() // false
```
ここで各自の所有プロパティを見てみたら
```jsx
console.log(Object.getOwnPropertyNames(Person))
// [ 'length', 'prototype', 'staticMethod', 'name' ]
console.log(Object.getOwnPropertyNames(person))
// [ 'constructorMethod' ]
console.log(Object.getOwnPropertyNames(Person.prototype))
// [ 'constructor', 'classMethod' ]
```
明らかに`staticMethod`は`class Person`の所有メソッドになって、インスタンスに提供できませんでした。part2でわかったように、インスタンス`person`のプロトタイプチェーンは`Person.prototype`なので、`class Person`内部所有のメソッドアクセスできません。
（インスタンス`person`のプロトタイプチェーンは、
prototypeの`class Person`からの`this`オブジェクト
↓
`Person.prototype`
↓
`Object.prototype`
）
```jsx
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype) // true
```
`staticMethod()`は`class Person`の`constructor(){}`のプロパティではないため、このルーツには`staticMethod()`含まれていないし、`class Person`を経由して呼び出さねばならない状態になっています。

ここから少し別の話ですが、`extends`の子クラスとそのインスタンスだったらどうなるのでしょうか？
`extends`で親クラスへ拡張した子クラスなら、ルーツのなかに`class Person`があったので、親クラスの静的メソッドでもアクセスできます。
```jsx
//
class Taro extends Person { }

console.log(Object.getPrototypeOf(Taro) === Person) // true

// Taro.constructorMethod()
// TypeError: Taro.constructorMethod is not a function

Taro.staticMethod() // false

// Taro.classMethod()
// TypeError: Taro.classMethod is not a function
```
`Taro`の`extends`のプロトタイプチェーンは：
`Taro`
↓
`Person`
↓
`Function.prototype`
このルーツでは`class Person`を経由していた。`class Person`所有の静的メソッドが使えるけど、`constructor()`と、`Person.prototype`へ移行する`class`内部のメソッドが使えません。

```jsx
console.log(Object.getPrototypeOf(Taro) === Person) // true
console.log(Object.getPrototypeOf(Person) === Function.prototype) // true
```
なら`extends`のインスタンスはどうなったのでしょう？
`extends`とインスタンスのメリットを兼ねてどちらのメソッドでもアクセスできるかな？
```jsx
let taro = new Taro()

taro.constructorMethod() // false

// taro.staticMethod()
// TypeError: taro.staticMethod is not a function

taro.classMethod() // false
```
```jsx
console.log(Object.getPrototypeOf(taro) === Taro.prototype) // true
console.log(Object.getPrototypeOf(Taro.prototype) === Person.prototype) // true
```
結局無理でした。インスタンス`taro`は
`class Taro`から`class Person`の`this`オブジェクトを継承、
↓
`Taro.prototype`
↓
`Person.prototype`。

`taro.constructorMethod()`は`this`オブジェクトから、
`taro.classMethod()`は`Person.prototype`からアクセスできましたが、
しかしどちらでも`class Person`へ経由することが不可能なので`staticMethod()`は無理でした。

JavaScriptの継承の仕方を完全に理解するために、最初の`this`から、`.__proto`、`prototype`、インスタンスそして`class`、ここまできたら`extends`の拡張、プロトタイプチェーン、根本的に別のものだと感じています。

自分の母国語は中国語で、原文の英語はもちろん、日本語の文章も参考しながら自分の検証を加えて勉強してきました。ほかのプログラミング言語は大した勉強したことないのでこういう理解は適切かなー、こんなこと書いていいかなー、本当に不安でした。
でも、たぶんみんなの中でもともと継承やら拡張やら定義が違っていて、自分の納得できる結論にしててもいいではないかと。

自分の中では`extends`はあくまでもフィールドの拡張、そしてプロトタイプチェーンは継承のルーツにしたほうが納得できる分け方だと思います。

ではどうやって静的メソッドをアクセスするでしょうか。`this`を使うのと、使わない方法をやってみたいと思います。

# Static with using `this` binding
```jsx
class Person {
  constructor(age = 18, favoriteColor = 'red') {
    this.age = age
    this.favoriteColor = favoriteColor
  }

  static change(age, favoriteColor) {
    this.age = age
    this.favoriteColor = favoriteColor
    console.log(this)
    return `I am ${this.age}. My favorite color is ${this.favoriteColor}`
  }
}

let person1 = new Person()
console.log(person1)
// Person { age: 18, favoriteColor: 'red' }

person1.age = 20
console.log(person1)
// Person { age: 20, favoriteColor: 'red' }

console.log(person1.change(20))
// TypeError: person1.change is not a function
```
`person1.age`値を差し替えるなら直接値を変えていいですが、静的メソッドをアクセスできません。

下のようにバインドメソッドを使って、
```jsx
console.log(Person.change.call(person1, 30, 'blue'))
// Person { age: 30, favoriteColor: 'blue' }
// I am 30. My favorite color is blue
```
```jsx
console.log(Person.change.apply(person1, [40, 'yellow']))
// Person { age: 40, favoriteColor: 'yellow' }
// I am 40. My favorite color is yellow
```

静的メソッド`change()`は`class Person`を通して呼び出さねばならなかったので、thisオブジェクトの`age`が差し替えられるように、`change(age, favoriteColor)`の内部に`this.age = age`、`this.favoriteColor = favoriteColor`を書いておきました。

それでバインドメソッド`call()`、`apply()`で関数を強制的に別の引数に参照させたり、あるいは下にある、別の例のようにオブジェクトコンテキストを参照させたりするのもできます。
```jsx
class CallObj {
  constructor(age = 18, favoriteColor = 'red') {
    this.age = age
    this.favoriteColor = favoriteColor
  }

  static change() {
    console.log(this)
    return `I am ${this.age}. My favorite color is ${this.favoriteColor}`
  }
}

const obj = {
  age: 50,
  favoriteColor: 'white'
}

console.log(CallObj.change())
// [class CallObj]
// I am undefined. My favorite color is undefined

console.log(CallObj.change.call(obj))
// { age: 50, favoriteColor: 'white' }
// I am 50. My favorite color is white
```
インスタンスもオブジェクトだから同じこともできます。
```jsx
let callObj = new CallObj(60, 'black')
console.log(callObj)
// CallObj { age: 60, favoriteColor: 'black' }

console.log(CallObj.change.call(callObj))
// CallObj { age: 60, favoriteColor: 'black' }
// I am 60. My favorite color is black

callObj.age = 70
console.log(CallObj.change.apply(callObj))
// CallObj { age: 70, favoriteColor: 'black' }
// I am 70. My favorite color is black

console.log(CallObj.change())
// [class CallObj]
// I am undefined. My favorite color is undefined
```
ただ、なぜか`bind()`メソッドだけは変な動きがあって、調べてもまだ原因が分からなくて、課題として残しておきます。
```jsx
console.log(CallObj.change.bind(callObj))
// [Function: bound change]
```

# Static without using `this`
```jsx
class Person {
  constructor(age = 18, favoriteColor = 'red') {
    this.age = age
    this.favoriteColor = favoriteColor
  }

  static change(obj, age, favoriteColor) {
    obj.age = age
    obj.favoriteColor = favoriteColor
    console.log(this)
    return `I am ${this.age}. My favorite color is ${this.favoriteColor}`
  }
}

let person4 = new Person(20, 'blue')
console.log(person4)
// Person { age: 20, favoriteColor: 'blue' }

console.log(Person.change(person4, 30, 'yellow'))
// [class Person]
// I am undefined. My favorite color is undefined
console.log(person4)
// Person { age: 30, favoriteColor: 'yellow' }
```
この方法、正直おすすめしません。（調べるなか出てきた方法ですが、仕組みが理解したら、なぜかくどくてもっと直観的な書き方があると思った。）
私から見れば、静的メソッドを利用してインスタンスのプロパティバリューを変えようとしたら、[Static with using `this` binding](https://qiita.com/drafts/e4529a08e902dcd12a6e/edit#static-with-using-this-binding)のように値を書き換えたり、あるいは`call()`や`apply()`で変えればいいのではないかと。もしくは一番根本的に、`constructor()`の引数として入れればいいと思います。

大量生産を考えたら一番やりやすいのはたぶん、先にJSONのような固定したフォーマットのオブジェクトを立てて、ループで`call()`使ってオブジェクト要素を走査し、それならインスタンスが使わなくメモリーにも優しいし、静的メソッドのアクセス権限（`extends`）をどこの子クラスにあげたかをいちいち覚えなくてもよいのではないかと。（ただの推測、実際テストしたことがありません。）

# Class fileds
## Public class fileds
ES6には、まずJavaのようにprivate variableを設定できないので、区分するために`_`(underscore)を先頭に書くが、実際に値のアクセスや変更ができます。
```jsx
class IncreasingCounter {
  constructor() {
    this._count = 0
  }
}

const counter = new IncreasingCounter()
console.log(counter)
// IncreasingCounter { _count: 0 }

counter._count = 10
console.log(counter)
// IncreasingCounter { _count: 10 }
```
```jsx
class IncreasingCounter {
  constructor() {
    this._count = 0
  }
  getValue() {
    return `the current value is ${this._count}`
  }
  increment() {
    this._count++
    console.log(this)
  }
}

const counter = new IncreasingCounter()
console.log(counter.getValue())
// the current value is 0

counter.increment()
// IncreasingCounter { _count: 1 }
console.log(counter.getValue())
// the current value is 1

console.log(Object.getOwnPropertyNames(IncreasingCounter.prototype))
// [ 'constructor', 'getValue', 'increment' ]
console.log(Object.getOwnPropertyNames(counter))
// [ '_count' ]
```
[こちらの書き方](https://v8.dev/features/class-fields#public-class-fields)のように、`constructor()`使わず書いても同じです。
```jsx
class IncreasingCounter {
  _count = 0

  getValue() {
    return `the current value is ${this._count}`
  }
  increment() {
    this._count++
    console.log(this)
  }
}

const counter = new IncreasingCounter()
console.log(counter.getValue())
// the current value is 0

counter.increment()
// IncreasingCounter { _count: 1 }
console.log(counter.getValue())
// the current value is 1

console.log(Object.getOwnPropertyNames(IncreasingCounter.prototype))
// [ 'constructor', 'getValue', 'increment' ]
console.log(Object.getOwnPropertyNames(counter))
// [ '_count' ]
```
（単に`class`フィールドに書いて、`constructor()`にもう一度書いたら同じプロパティであれば最終的に上書きされるのですが。）

つまり`_`を使っても使わなくても、`class`フィールドでも、`constructor()`でも`Public`な状態であることがわかった。
（`static`で扱われた静的メソッド/プロパティも同じく、**Public**な状態で自由に呼び出すことができます。）

## Private class fileds
**Private**状態にしてほしいなら、`#`を使えば制限できます。
```jsx
class IncreasingCounter {
  #count = 0

  getValue() {
    return `the current value is ${this.#count}`
  }

  increment() {
    this.#count++
    console.log(this)
  }
}

const counter = new IncreasingCounter()
console.log(counter.getValue())
// the current value is 0

counter.increment()
// IncreasingCounter { }
console.log(counter.getValue())
// the current value is 1
```
`class`フィールド内部なら、`#count`へアクセスや値の変更などでき、関連のある関数も正しく動作してくれましたが、

```jsx
// console.log(counter.#count)
// SyntaxError: Private field '#count' must be declared in an enclosing class
// console.log(IncreasingCounter.#count)
// SyntaxError: Private field '#count' must be declared in an enclosing class

console.log(Object.getOwnPropertyNames(IncreasingCounter.prototype))
// [ 'constructor', 'getValue', 'increment' ]
console.log(Object.getOwnPropertyNames(counter))
// [ ]
```
外部からのアクセスであれば`class IncreasingCounter`ではプロパティが見つからないし、見つけることもできません。

## Public & private static properties
なので、静的メソッドやプロパティも`#`使えば設立できます。
```jsx
class IncreasingCounter {
  static #count = 1

  static #digit = 1

  static #digits() {
    return IncreasingCounter.#count * (10 ** IncreasingCounter.#digit)
  }

  addDigit() {
    IncreasingCounter.#digit++
  }

  getResult() {
    return IncreasingCounter.#digits()
  }
}

let counter = new IncreasingCounter()

console.log(counter)
// IncreasingCounter {}
counter.addDigit()
console.log(counter.getResult())
// 100
```
`extends`で拡張したらどうなるかな。
```jsx
class TestCounter extends IncreasingCounter {
  test() {
    // return `the current value is ${TestCounter.#count}`
    // SyntaxError: Private field '#count' must be declared in an enclosing class
    // return `the current value is ${IncreasingCounter.#count}`
    // SyntaxError: Private field '#count' must be declared in an enclosing class
    // return `the current value is ${this.#count}`
    // SyntaxError: Private field '#count' must be declared in an enclosing class

    return `the result is ${this.getResult()}`
  }
}

let testCounter = new TestCounter()
console.log(testCounter.test())
// the result is 100
```
どうやらprivate状態のプロパティやメソッドが子クラスでは使用できませんでした。
うっかりして書き替えられたくないプロパティ、メソッドはprivateで一か所に集中管理したり、動作させたり、結果だけPublicのメソッドで取り出すようにしたら、何か安全かつ色んなことができて、面白いですね。

# ほかの書き方等々
分割代入でデフォルト値の練習です。
```jsx
class Person {
  constructor({ age = 18, favoriteColor = 'red' } = {}) {
    this.age = age
    this.favoriteColor = favoriteColor
  }

  static change(age) {
    this.age = age
    return `I am ${this.age}.`
  }
}

let person1 = new Person()
console.log(person1.age) // 18
// console.log(person1.change())
// TypeError: person2.change is not a function

let person2 = new Person({})
console.log(person2.age) // 18
console.log(person2)
// Person { age: 18, favoriteColor: 'red' }

let person3 = new Person({ age: 20 })
console.log(person3.age) // 20
console.log(person3)
// Person { age: 20, favoriteColor: 'red' }
```