【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
参考文献
- Window.localStorage - Web API | MDN (2019/09/01)