JavaScriptのClassについて part1

# 初めに
今回はES6の`Class`の基礎概念をまとめていきたいと思います。

主な参考文章：
[Class basic syntax - javascript.info](https://javascript.info/class)
[Are ES6 classes just syntactic sugar for the prototypal pattern in Javascript? - stackoverflow](https://stackoverflow.com/questions/36419713/are-es6-classes-just-syntactic-sugar-for-the-prototypal-pattern-in-javascript)

# Classとは
同じオブジェクトを大量に作成するテンプレートです。
コンストラクタ関数と`new`インスタンスの関係に似ています。MDNの説明では、
>class キーワードは ES6 で導入されましたが、シンタックスシュガーであり、JavaScript は引き続きプロトタイプベースです
[継承とプロトタイプチェーン](https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#inheritance_with_the_prototype_chain)

たしかに`class`はコンストラクタ関数の動きと似ていますが、構造としては違いがあります。それにただのシンタックスシュガーではありません。

# syntax
今回デモのコードです。
```jsx
class Person {
  constructor(name) {
    this.name = name
  }
  nation = 'Japan'

  greeting() {
    return `Hello, ${this.name}`
  }
}
```
コンストラクタ関数と比べるためのコードです。
```jsx
function Person(name) {
  this.name = name
  this.nation = 'Japan'

  this.greeting = function () {
    return `Hello, ${this.name}`
  }
}
```
まずは`class`への検証です。
上は`class Person`、下は`function Person`のインスタンス、
```jsx: class vs. constructor function
// class
console.log(typeof Person) // function
console.log(Person.prototype.constructor) // [class Person]
console.log(Object.getPrototypeOf(Person) === Function.prototype) // true
console.log(Person instanceof Function) // true
console.log(Object.getOwnPropertyNames(Person.prototype))
// [ 'constructor', 'greeting' ]

// constructor function
console.log(typeof Person) // function
console.log(Person.prototype.constructor) // [Function: Person]
console.log(Object.getPrototypeOf(Person) === Function.prototype) // true
console.log(Person instanceof Function) // true
console.log(Object.getOwnPropertyNames(Person.prototype))
// [ 'constructor' ]
```
`class`も`constructor function`も`Function`のインスタンスであり、プロトタイプチェーンも`Function.prototype`とつながっています。

しかし`Person.prototype`では、なかみの構成が違います。どちらもインスタンスを創れるので必ず`constructor`を持っていますが、`class`オブジェクトの中にある関数は`constructor`によって定義されていませんので、`class Person`の`.prototype`オブジェクトに移行され、また、`class Person`の`.prototype`で新しいプロパティやメソッドを書き込みできない`read-only`になります。
下のコード見てみましょう。
```jsx
function Test() {
  this.hello = function hello() { }
}
const test = new Test()
console.log(test.hello) // [Function: hello]
console.log(Object.getPrototypeOf(test.hello) === Function.prototype) // true

const Hello = test.hello
const newHello = new Hello()
console.log(newHello) // hello {}
```
コンストラクタ関数なら、中の関数がまた新しいインスタンスを創ることができる。それ自体が`constructor`に定義されているから。
```jsx
class Test {
  hello() { }
}
const test = new Test()
console.log(test.hello) // [Function: hello]
console.log(Object.getPrototypeOf(test.hello) === Function.prototype) // true

const Hello = test.hello
const newHello = new Hello()
// TypeError: Hello is not a constructor

// note: [[IsClassConstructor]]
```
しかし`class`はオブジェクト中の関数を、自分の`prototype`に移行して保存するので**read-only**読み取りのみの状態になり、`new`でインスタンスを創ることができませんでした。

ほかにも異なるところがありますが、[class vs. constructor function](https://qiita.com/drafts/c0e00a35e54e0bac56c6/edit#class-vs-constructor-function)にご参考になればと。

## getter/setter
オブジェクトのgetter/setterは`class`でも使えます。
[JavaScriptのObjectについて -- get & set](https://qiita.com/hu-yu/items/c049c19365e2f503ff35#get--set)
```jsx
class Person {
  constructor(name, nation) {
    this.name = name
    this.nation = nation
  }

  get greeting() {
    return `Hello, I am ${this._name}, I come from ${this._nation}`
  }

  set name(value) {
    if (value.length < 1) {
      console.log('Name is too short.')
      return
    }
    this._name = value
  }

  set nation(value) {
    if (value < 2) {
      console.log('Nation name is too short.')
      return
    }
    this._nation = value
  }
}

const Taro = new Person('Taro', 'Japan')
console.log(Taro)
// Person { _name: 'Taro', _nation: 'Japan' }
console.log(Taro.greeting)
// Hello, I am Taro, I come from Japan
```
`class`の`get/set`は`constructor()`から返されたオブジェクト`Person { _name: 'Taro', _nation: 'Japan' }`のプロパティをアクセスして使用する。

しかし`Taro`には`get`の`greetring`関数が持ってないのに、どこへアクセスしたんでしょうか。
```jsx
console.log(Object.getOwnPropertyNames(Taro))
// [ '_name', '_nation' ]
console.log(Object.getOwnPropertyNames(Person.prototype))
// [ 'constructor', 'greeting', 'name', 'nation' ]
```
やはり`get/set`も`Person.prototype`に移行されました。
```jsx
console.log(Object.getOwnPropertyDescriptors(Person.prototype))
// {
//   constructor: {
//     value: [class Person],
//       writable: true,
//         enumerable: false,
//           configurable: true
//   },
//   greeting: {
//     get: [Function: get greeting],
//       set: undefined,
//         enumerable: false,
//           configurable: true
//   },
//   name: {
//     get: undefined,
//       set: [Function: set name],
//         enumerable: false,
//           configurable: true
//   },
//   nation: {
//     get: undefined,
//       set: [Function: set nation],
//         enumerable: false,
//           configurable: true
//   }
// }
```
## methods in class constructor
関数を`class`の`constructor`で宣言したらどうなるでしょう。さきほどの例を使いってみたら、
```jsx
class Person {
  constructor(name, nation) {
    this.name = name
    this.nation = nation
    this.test = function () {
      return `${this._name} ${this._nation}`
    }
  }
...
}

console.log(Object.getOwnPropertyNames(Taro))
// [ '_name', '_nation', 'test' ]
console.log(Object.getOwnPropertyNames(Person.prototype))
// [ 'constructor', 'greeting', 'name', 'nation' ]
console.log(Object.getOwnPropertyDescriptors(Taro))
// {
//   _name: {
//     value: 'Taro',
//     writable: true,
//     enumerable: true,
//     configurable: true
//   },
//   _nation: {
//     value: 'Japan',
//     writable: true,
//     enumerable: true,
//     configurable: true
//   },
//   test: {
//     value: [Function (anonymous)],
//     writable: true,
//     enumerable: true,
//     configurable: true
//   }
// }
```
`constructor()`で定義されたら`Person.prototype`に移行されず、そして一般の関数のように使えて、`new`でインスタンスを創ることもできます。
```jsx
const test = new Taro.test()
console.log(test) // {}
```

さらにいろいろ試してみますと、
```jsx
class Person {
  constructor(name, nation) {
    this.name = name
    this.nation = nation
  }
...
  test() {
    return `${this._name} ${this._nation}`
  }
}

console.log(Object.getOwnPropertyNames(Taro))
// [ '_name', '_nation' ]
console.log(Object.getOwnPropertyNames(Person.prototype))
// [ 'constructor', 'greeting', 'name', 'nation', 'test' ]
console.log(Taro.test()) // Taro Japan
```
さきの結論と同じ、関数`test()`も`Person.prototype`に移行。
しかしもし変数に入れられたら、
```jsx
class Person {
...
...
  test = function test() {
    return `${this._name} ${this._nation}`
  }
}
console.log(Object.getOwnPropertyNames(Taro))
// [ 'test', '_name', '_nation' ]
console.log(Object.getOwnPropertyNames(Person.prototype))
// [ 'constructor', 'greeting', 'name', 'nation' ]
console.log(Taro.test()) // Taro Japan
```
まとめてみると、`constructor()`内部に宣言した関数や、`class`で変数で保存する関数は`constructor()`に定義され、普通の関数のようにインスタンスができます。
`class`内部で書いた宣言不要？の関数（`get/set`も）`.prototype`に移行されて、再定義できないメソッドになります。

（その違いで何が起こるかまだわかりませんが、とりあえずここで記録として残します）

# class vs. constructor function
ここからは`class`とコンストラクタ関数の違いをまとめていきたいと思います。

## methods is non-enumerable or enumerable in class
さきの例を再利用して検証してみると、
```jsx
class Person {
  constructor(name, nation) {
    this.name = name
    this.nation = nation
    this.test = function () {
      return `${this._name} ${this._nation}`
    }
  }
...
}
for (let key in new Person('Taro', 'Japan')) {
  console.log(key)
}
// _name
// _nation
// test

//
class Person {
...
...
  test = function test() {
    return `${this._name} ${this._nation}`
  }
}

for (let key in new Person('Taro', 'Japan')) {
  console.log(key)
}
// test
// _name
// _nation
```
```jsx
class Person {
...
...
  test() {
    return `${this._name} ${this._nation}`
  }
}

for (let key in new Person('Taro', 'Japan')) {
  console.log(key)
}
// _name
// _nation
```

`constructor()`で定義されたら　⇒　enumerable
`class`で定義されて`.prototype`に移行　⇒　non-enumerable
（そもそも`Person`のなかにいなくなったから、列挙できないとは言えない気がする）

## classes hoisted with uninitialized state
すべての宣言は**hoisting**状態になります、`class`も同じく。ただ`class`は`function`のようにいつでも呼び出されていいのでなく、
`let/const`と同じ`TDZ`に`uninitialized`状態になるので、`class`より前の呼び出しは`ReferenceError`投げてくるのです。
```jsx
console.log(a)
// ReferenceError: Cannot access 'a' before initialization
class a {}
```
[もう一度JavaScriptを理解する part1 -- let/const](https://qiita.com/hu-yu/items/c52499418cb448783ac4#letconst)

なので、`class`は`var`と`function`のように再宣言することもできません。
```jsx
class a { }
class a { }
// SyntaxError: Identifier 'a' has already been declared
```

```jsx
class a { }
{
  class a { } // it is work
  class b { }
}

new b()
// ReferenceError: b is not defined
```
もちろん別のスコープであれば同じ名前でもいけました。
これも当たり前ですが外部から内部へのアクセスはダメで、内部から外部へのアクセスは大丈夫です。
```jsx
class b { }
{
  new b()
}
```

## block scope
```jsx
const name = 'Taro'

class testScope {
  name = name
  nation = 'Japan'
  constructor(birthPlace) {
    this.birthPlace = birthPlace
  }

  scope() {
    return `${this.nation} ${this.birthPlace}`
  }
}

const test = new testScope('Tokyo')
console.log(test)
// testScope { name: 'Taro', nation: 'Japan', birthPlace: 'Tokyo' }
console.log(test.name) // Taro
console.log(test.scope()) // Japan Tokyo
```
**function scope**と違って、`class`は**block scope**なので、`name`のように外部の変数へのアクセスは可能です。

下は**function scope**の例です。
```jsx
const name = 'Taro'
function testScope() {
  const name = name
  return name
}

console.log(testScope())
// ReferenceError: Cannot access 'name' before initialization
```