JavaScriptのコンストラクタとかいう闇深いものとprivate

# JavaScriptのコンストラクタとかいう闇深いものとprivate

js初心者が勉強中にコンストラクタの変な挙動を見つけて気になったので少し実験をしてみた。

## 目次
 - [定義の仕方多すぎる問題](#定義の仕方多すぎる問題)
 - [自身のクラスのインスタンス以外を戻り値にできる問題](#自身のクラスのインスタンス以外を戻り値にできる問題)
 - [継承するとconstructorが壊れる問題](#継承するとconstructorが壊れる問題)
 - [これを使ってなにができるか](#これを使ってなにができるか)
 - [最終結果](#最終結果)

## constructorとは
> オブジェクトの生成と初期化のための特殊なメソッドです。

要はnewしたときに自動で実行される特別なメソッドです

## 定義の仕方多すぎる問題
動くパターンと動きそうで動かないパターンがあるのでまずは動くパターン

### 動くパターン
#### 関数
```js:動くパターン
function A() {
    console.log('class A')
}

console.log(new A)
// class A
// A {}
```

これはA自体がコンストラクタになっているパターン

#### クロージャ
```js:動くパターン
var B = function() {
    console.log('class B')
}

console.log(new B)
// class B
// B {}
```
これもAと同じです

#### クラス
```js:動くパターン
class D {
    constructor() {
        console.log('class D')
    }
}

console.log(new D)
// class D
// D{}
```
クラスDにconstructorメソッドを定義するパターン
しかし以下の方法はコンストラクタとして正しく定義できません


### 動かないパターン

#### アロー関数
```js:動かないパターン
var C = () => {
    console.log('class C')
}

console.log(new C)
// Uncaught TypeError: C is not a constructor
```

>アロー関数式 は、その名のとおり矢印を使って記述し、function 式より短い構文で同様な内容を記述することができます。なおthis, arguments, super, new.target を束縛しません。また、アロー関数式は、メソッドでない関数に最適で、コンストラクタとして使うことはできません。
[アロー関数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions)


#### 関数にconstructorメソッドを定義
```js:動かないパターン
function E() {
    this.constructor = function() {
        console.log('class E')
    }
}

console.log(new E)
// E {constructor: ƒ}
```
Eのconstructorメソッドは実行されません(なんで)

#### 関数のprototypeにconstructorメソッドを定義
```js:動かないパターン
function F() {
}
F.prototype.constructor = function() {
    console.log('class F')
}

console.log(new F)
// F {}
// __proto__:
// constructor: ƒ ()
```
この場合もFのconstructorメソッドは実行されません(なんで)

## 自身のクラスのインスタンス以外を戻り値にできる問題
new演算子について調べるとこんな記述が

> コンストラクタ関数が返すオブジェクトが、new 式の結果になります。コンストラクタ関数が明示的にオブジェクトを返さない場合は、ステップ 1 で生成したオブジェクトを代わりに使用します。(通常、コンストラクタは値を返しませんが、通常のオブジェクト生成プロセスをオーバーライドしたい場合はそのようにすることができます)
[new](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new)

どうやらオブジェクトを返すと通常とは違う動きをするらしい

## コンストラクタに戻り値を持たせてみた

```js:戻り値がオブジェクト以外
class G {
    constructor() {
        return 1
    }
}

console.log(new G)
// G {}
```
オブジェクト以外の場合は戻り値が無視されています

```js:戻り値がオブジェクト
class H {
    constructor() {
        return {hoge:'hoge'}
    }
}

console.log(new H)
// {hoge:'hoge'}
```
戻り値が返って来る
Hはどこかへ行ってしまった。。。
※配列の場合も同じ挙動になります


### メソッドやプロパティはどうなるのか

```js
class I {
    constructor() {
        this.hoge = 'hoge'
        return {}
    }

    fuga() {
        return 'fuga'
    }
}

var i = new I
console.log(i)
// {}
console.log(i.hoge)
// undefined
console.log(i.fuga())
// Uncaught TypeError: i.fuga is not a function
```

まったく別のオブジェクトが返ってくるのでプロパティやメソッドにアクセスできない
だがプロパティやメソッドを戻り値のオブジェクトに持たせることはできる様子

```js:戻り値のオブジェクトにプロパティやメソッドを持たせる
class J {
    constructor() {
        this.hoge = 'hoge'
        return {
            hoge : this.hoge,
            fuga : this.fuga,
        }
    }

    fuga() {
        return this.hoge
    }
}

var j = new J
console.log(j)
// {hoge: "hoge", fuga: ƒ}
console.log(j.hoge)
// hoge
console.log(j.fuga())
// hoge
```
上記の例の戻り値のプロパティとメソッドは**戻り値のオブジェクト自身が持っている**ことになっている
つまり戻り値のhogeプロパティはJクラスから分離されている
fugaメソッド内でつかう**thisは戻り値のオブジェクトを参照する**

## 継承するとconstructorが壊れる問題
以下の、コンストラクタでオブジェクトを返す親クラスがあったとする

```js:親クラス
class Parent {
    constructor() {
        return {}
    }
}
```

このParentクラスを継承して新しいクラスをつくる
そしてコンストラクタ内でプロパティを定義する

```js:子クラス
class Child extends Parent {
    constructor() {
        super()
        console.log(this)
        this.hoge = 'hoge'
    }
}

console.log(new Child)
// {}
// {hoge:'hoge'}
```
コンストラクタ内の三行に注目していただきたい

**1行目**
この行は親クラスのコンストラクタを実行している
これがないと**エラー**になる
>Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

thisを使いたければスーパークラスのコンストラクタを実行しろ的な

**2行目**
1行目のおかげでthisが使えるようになったので確認してみると中身は親の**コンストラクタが返した**空のオブジェクト。
つまり一行目では**`var this = super()`**のようなことがされている感じ

**3行目**
2行目から分るとおりthisは親のコンストラクタが返した空のオブジェクトなので**Childクラスにプロパティを定義することはできない**
Childクラスに**他のメソッドを定義してあっても呼び出すことはできない**

このようにコンストラクタでオブジェクトを返すと**継承先のコンストラクタを汚染する**ことになる

## これを使ってなにができるか
privateを実装することができるぽい？

### 基本概念
Bridgeパターンのような(Bridgeパターンをよく分かってない。。。)**橋渡しをしてくれるオブジェクト**を、コンストラクタで返す
ただしそのままオブジェクトにプロパティやメソッドを定義していってもthisを指すものがコンストラクタで返すオブジェクト自身になってしまうため工夫をする

### メソッド
[bind()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)を使ってthisが何を指すのかを指定する

```js
...
    constructor() {
        this.name = 'hoge'
        return {
            getName : this.getName.bind(this)
        }
    }

    getName() {
        return this.name
    }
...
```
これでgetName内で、コンストラクタの戻り値で返していないthis.nameを指せるようになった

これがコンストラクタでprivateを実装する基本的な方法です
ようは**publicにしたいものを定義していく**方法です

しかしこのままでは**asyncメソッドは定義できない**
なので改造

```js
...
    constructor() {
        this.name = 'hoge'
        return {
            getName : function() {
                return this.getName()
            }.bind(this)
        }
    }

    async getName() {
        return this.name
    }
...
```
これでasyncメソッドでも実行結果を返すので問題ない(はず)

### プロパティ
安直に以下のようにすると正しく動かない

```js
...
    constructor() {
        this.name = 'hoge'
        return {
            name : this.name
        }
    }
...
```

`this.name`の**値**を格納しているので、外から値を変更できない
なので改造

```js
...
    constructor() {
        this.name = 'hoge'
        let bridge = {}
        Object.defineProperty(bridge, 'name ', {
            get: function() {
                return this.name
            }.bind(this),
            set: function(arg) {
                this.name = arg
            }.bind(this)
        })
        return bridge
    }
...
```
[Object.defineProperty](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)を使ってコンストラクタで返すオブジェクトに`getter` `setter`メソッドを定義して`bind()`する

これで外でプロパティを変更しても反映される

### 継承してコンストラクタを使いたいときの救済措置
この方法でprivateを表現するとコンストラクタが壊れてしまう
これはどうしようもないです
なので救済措置

コンストラクタ内のthisが汚染されるだけなので、`_init_()`メソッド(名前はなんでもいいです)を別に用意してこれをコンストラクタ代わりにする

[可変長引数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters)で引数の数は子クラスに自動であわせられる

```js:親クラス
...
    constructor(...initArgs) {
        this._init_(...initArgs)
        return {
            ...
        }
    }

    _init_() {}
...
```

```js:子クラス
...
    _init_(argA, argB) {
        super._init_()
        this.hoge = argA
        this.fuga = argB
    }
...
```


でもpublicで返したいものは全部親クラスがなんとかしないといけない
**継承したら子クラスのプロパティやメソッドpublicにできないよね...**
親クラスが子クラスの実装内容知ってたら気持ち悪いし
**ていうかいちいちpublicなやつ定義するのめんどくさいよね...**

だから全部自動でpublicなやつ定義する

## 最終結果
簡単にprivateできるクラスが作れます

**使い方**

 - 下記のクラスを継承する
 - 一文字目が`_`、二文字目が`_`以外のプロパティとメソッドはprivateになる
  - `this.hoge`はパブリック
  - `this._hoge`はプライベート
 - 初期化処理は`_init_()`を使う

### コード
```js
class Base {
    constructor(...initArgs) {
        // 継承先用の独自コンストラクタ
        this._init_(...initArgs)

        // コンストラクタで返すオブジェクト
        let bridge = {}

        // プロパティとメソッド全部取得
        let obj = this
        let propNames = []
        while (obj) {
            propNames = propNames.concat(Object.getOwnPropertyNames(obj))
            obj = Object.getPrototypeOf(obj) // __proto__も全部見る
        }

        // privateのパターン
        let privatePattern = /^_[^_].*/
        // publicにするやつ全部定義する
        for (let propName of new Set(propNames)) {
            if (privatePattern.test(propName) || propName === '__proto__') {
                // privateのパターンにマッチするか__prop__のときはcontinue
                continue
            }

            if (typeof this[propName] === 'function') {
                // 関数の場合
                bridge[propName] = function(...args) {
                    return this[propName](...args)
                }.bind(this)
            } else {
                // プロパティ(getterも含む)
                Object.defineProperty(bridge, propName, {
                    get: function() {
                        return this[propName]
                    }.bind(this),
                    set: function(arg) {
                        this[propName] = arg
                    }.bind(this)
                })
            }
        }
        return bridge
    }

    _init_() {}
}
```

### 問題点
 - このクラスを継承しなければならない
 - コンストラクタが汚染されるので代わりの`_init_()`を使わなければならない
 - privateなstaticメソッドは定義できない
 - インスタンスした結果をconsole.logしてもどのクラスなのか分りにくい
  - メソッドの[[BoundThis]]を見れば一応分る
 - インスタンスするたびにオブジェクトつくるのでたくさんインスタンスするのは重そう
  - bridgeをstaticプロパティにしてうまくディープコピーできればよさげ(やり方教えてください)
 - **IEは知らないです**

## 最後に
js難しいです。
間違っていることや足りないことがあれば教えていただけると嬉しいです
~~`__init__()`はPythonのコンストラクタの名前をもらいましたが隠蔽したほうがよさそうなので`_init_()`とかのほうがいいかもです~~
`_init_()`にしました

**全然privateじゃありませんでしたこれprotectedでした^^**