LoginSignup
1
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-29

前回の続き。
前回の例と違うのは、クラス内からsetTimeoutを呼んでるのと、オブジェクト内のオブジェクトのメソッドを呼んでること。

setTimeout関数の失敗例 その2

<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

undefinedが返ってきた。

成功例

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

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

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

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

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

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

<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内メソッドのアロー関数化(プロトタイプ対応版)

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

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

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

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

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1