Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

前回の続き。
前回の例と違うのは、クラス内から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);
            }
        }

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away