javascriptでオブジェクト内にオブジェクトを作ったらsetTimeout関数のthis問題が複雑化した

[前回](https://qiita.com/querykuma/items/d48d3cb1312c0b0cde58)の続き。
前回の例と違うのは、クラス内から`setTimeout`を呼んでるのと、オブジェクト内のオブジェクトのメソッドを呼んでること。

# setTimeout関数の失敗例 その２

```html
<script>
    document.addEventListener('DOMContentLoaded', function () {
        'use strict';
        class Test {
            constructor() { this.value = "成功"; }
            alert() {
                alert(this.value);
            }
        }
        class TestWrapper {
            constructor() {
                this.test = new Test(); //オブジェクト内にオブジェクトを作成
            }
            setTimeoutWrap() {
                setTimeout(this.test.alert.bind(this), 100); //失敗例
            }
        }

        const testwrapper = new TestWrapper(); //オブジェクトを作成
        test01.addEventListener('click', function () {
            testwrapper.setTimeoutWrap();
        });
    });
</script>
<button id="test01">test01</button>
```

このプログラムを実行すると

![undefined.png](https://qiita-image-store.s3.amazonaws.com/0/311399/d1f19213-b79a-a235-87d6-09876afbc847.png)

undefinedが返ってきた。


# 成功例

どのように書けばいいかというと、`setTimeout(this.test.alert.bind(this.test), 100);`と書けば成功。setTimeoutで呼び出すメソッドが連鎖する場合、bindするオブジェクトも連鎖させなければならないようだ。

オブジェクトの４段連鎖なら、`setTimeout(this.test.test2.test3.alert.bind(this.test.test2.test3), 100);`となる :confounded:。

`setTimeout(function () { this.test.alert() }.bind(this), 100);`と書いても成功。前回書いたように、括弧を付けないとオブジェクトのメソッドが関数化するので、括弧を付けて関数化させないほうがいい。オブジェクトの４段連鎖なら、`setTimeout(function () { this.test.test2.test3.alert() }.bind(this), 100);`となって`bind(this)`１つで済むので。

@cfm-artさんのコメントのように、`setTimeout(() => { this.test.alert() }, 100);`と書いても成功。アロー関数は関数に`bind(this)`を付ける効果があるようだ。

# class内メソッドのアロー関数化

アロー関数にすればそれが定義されている場所でのthisになるので、class内メソッドをアロー関数にできないか考えてみた。

```html
<script>
    document.addEventListener('DOMContentLoaded', function () {
        'use strict';
        class Test {
            constructor() {
                this.value = "成功";
                this.alert = () => {
                    alert(this.value);
                }
            }
        }
        class TestWrapper {
            constructor() {
                this.test = new Test(); //オブジェクト内にオブジェクトを作成
            }
            setTimeoutWrap() {
                setTimeout(this.test.alert, 100); //成功
            }
        }

        const testwrapper = new TestWrapper(); //オブジェクトを作成
        test01.addEventListener('click', function () {
            testwrapper.setTimeoutWrap();
        });
    });
</script>
<button id="test01">test01</button>
```

constructor内にメソッドを書く書き方が美しくないが、これはいいかもしれない :smirk_cat:。
オブジェクトは本来こうあるべきじゃあないだろうか。

この書き方でできなくなることもある。呼び出し側で関数に`bind(new_this)`とやってアロー関数内の`this`の値を書き換えられなかった。bind関数よりもアロー関数のほうが強い。

# class内メソッドのアロー関数化(プロトタイプ対応版)

```javascript
        class Test {
            constructor() {
                this.value = "成功";
                Test.prototype.alert = () => {
                    alert(this.value);
                }
            }
        }
```

コメントの指摘を受け、メソッドをプロトタイプに入れるように改善してみた。オブジェクトのインスタンス毎にメソッドを作るよりもプロトタイプ一箇所で作ったほうがいい。


```javascript
        class Test {
            constructor() {
                this.value = "成功";
                Test.prototype.alert = function() {
                    alert(this.value);
                }.bind(this);
            }
        }
```

アロー関数を使わずに書くことも可能だった。