Shion のもくログ(旧: Shion の技術メモ)

使った技術のメモや、うまくいかなかった事とかを綴ります

PR

【JavaScript】素のobject として扱われるケースのメモ

JavaScript のclass で実装していると、知らぬ間にobject の型が変わっていて、メソッド呼び出しができないケースがありました。 またTypeScript で型を与えても、実行時エラーになることがありました。

原因を一言で言えば、実行時に素のobject として扱われているからなのですが、 意識しないと見落とすので、備忘をかねてメモしていきたいと思います。

素のobject として扱われるケース

Angular のHttpClient

ジェネリクスで型指定していますが、実行時はany(素のObject) として扱われます。 なのでメソッドは諦めて、interface を使ってデータの型アサーションのみに留めるのが無難です。

実装例(TypeScript)

割愛

実行結果

割愛

localStorage の保存、取得処理

実装例(JavaScript)

+function () {
    /**
     * メッセージの保持クラス
     */
    class Message {
        /** メッセージの一覧データ */
        messageList

        /**
         * メッセージ一覧データのフォーマット済み文字列の取得
         */
        getFormattedMessage() {
            if (!Array.isArray(this.messageList) || this.messageList.length < 1) { return }
            return this.messageList.reduce((previous, current, index, _) => {
                return `${previous}Index: ${index}, Message: ${current}\n`
            }, "")
        }
    }


    // スクリプトの実行
    const data = new Message()
    data.messageList = [
        "aaa",
        "bbb",
        "ccc"
    ]
    console.log(`::original::\nIs Message Class?: ${data instanceof Message}\n${data.getFormattedMessage()}`)

    // 保存 → 取得
    localStorage.setItem("key", data)
    const stored = localStorage.getItem("key")
    try {
        // 型が復元されないので、メソッドが呼び出せず、エラーになる
        console.log(`::stored::\nIs Message Class?: ${stored instanceof Message}\n${stored.getFormattedMessage()}`)
    } catch (e) {
        console.error(`::stored::\nIs Message Class?: ${stored instanceof Message}\n${e}`)
    }
}()

実行結果

::original::
Is Message Class?: true
Index: 0, Message: aaa
Index: 1, Message: bbb
Index: 2, Message: ccc

VM111:39 ::stored::
Is Message Class?: false
TypeError: stored.getFormattedMessage is not a function

簡易的なDeepCopy

JSON.stringify(), JSON.parse() 前後で型が変わります。

実装例(JavaScript)

+function () {
    /**
     * メッセージの保持クラス
     */
    class Message {
        /** メッセージの一覧データ */
        messageList

        /**
         * メッセージ一覧データのフォーマット済み文字列の取得
         */
        getFormattedMessage() {
            if (!Array.isArray(this.messageList) || this.messageList.length < 1) { return }
            return this.messageList.reduce((previous, current, index, _) => {
                return `${previous}Index: ${index}, Message: ${current}\n`
            }, "")
        }
    }


    // スクリプトの実行
    const data = new Message()
    data.messageList = [
        "aaa",
        "bbb",
        "ccc"
    ]
    console.log(`::original::\nIs Message Class?: ${data instanceof Message}\n${data.getFormattedMessage()}`)

    // DeepCopy
    const copied = JSON.parse(JSON.stringify(data))
    try {
        // 型が復元されないので、メソッドが呼び出せず、エラーになる
        console.log(`::copied::\nIs Message Class?: ${copied instanceof Message}\n${copied.getFormattedMessage()}`)
    } catch (e) {
        console.error(`::copied::\nIs Message Class?: ${copied instanceof Message}\n${e}`)
    }
}()

実行結果

::original::
Is Message Class?: true
Index: 0, Message: aaa
Index: 1, Message: bbb
Index: 2, Message: ccc

VM118:36 ::copied::
Is Message Class?: false
TypeError: copied.getFormattedMessage is not a function

回避方法

データとメソッドを分離する

インスタンスにメソッド情報がないのがいけないので、 静的メソッドなどインスタンス外にロジックを持っていくのも一つの手です。

実装例(JavaScript)

+function () {
    /**
     * メッセージの保持クラス
     */
    class Message {
        /** メッセージの一覧データ */
        messageList

        /**
         * メッセージ一覧データのフォーマット済み文字列の取得
         */
        static getFormattedMessage(args) {
            if (!Array.isArray(args) || args.length < 1) { return }
            return args.reduce((previous, current, index, _) => {
                return `${previous}Index: ${index}, Message: ${current}\n`
            }, "")
        }
    }


    // スクリプトの実行
    const data = new Message()
    data.messageList = [
        "aaa",
        "bbb",
        "ccc"
    ]
    console.log(`::original::\nIs Message Class?: ${data instanceof Message}\n${Message.getFormattedMessage(data.messageList)}`)

    // DeepCopy
    const copied = JSON.parse(JSON.stringify(data))
    try {
        // 静的メソッドなので影響がなくなる
        console.log(`::copied::\nIs Message Class?: ${copied instanceof Message}\n${Message.getFormattedMessage(copied.messageList)}`)
    } catch (e) {
        console.error(`::copied::\nIs Message Class?: ${copied instanceof Message}\n${e}`)
    }
}()

実行結果

::original::
Is Message Class?: true
Index: 0, Message: aaa
Index: 1, Message: bbb
Index: 2, Message: ccc

VM134:34 ::copied::
Is Message Class?: false
Index: 0, Message: aaa
Index: 1, Message: bbb
Index: 2, Message: ccc

参考文献

PR