JavaScriptのObject.freeze()でネストされたオブジェクトは不変にできる?その限界とdeepFreezeによる解決策
Object.freeze()メソッドを使うと、オブジェクトを不変(イミュータブル)にすることができます。しかし、ネストされた(入れ子の)オブジェクトが含まれている場合は注意が必要です。Object.freeze()がイミュータブル性を保証できるのは、あくまで最も外側の親オブジェクトだけであり、内部の子(ネストされた)オブジェクトには一切アクセスしないため、入れ子のプロパティの変更までは防げません。
例1:ネストがない場合、freezeは正常に機能する
以下の例にはネストされたオブジェクトが存在しません。Object.freeze()メソッドはオブジェクト全体を凍結(フリーズ)するため、凍結後にもかかわらず「name」プロパティに新しい値を代入しても、元の値は変わりません。
<html>
<body>
<script>
const person = {
"name" : "Suresh",
"Country" : "India",
"Designation" : "Mayor"
}
document.write("Before freeze the name is" +" "+ person.name);
document.write("</br>");
Object.freeze(person);
person.name = "John";
document.write("After freeze the name is" +" "+ person.name);
</script>
</body>
</html>出力結果
Before freeze the name is Suresh After freeze the name is Suresh
このように、凍結済みオブジェクトのトップレベルプロパティへの代入は無視されます(厳格モード"use strict"ではTypeErrorが発生します)。そのため「name」は"Suresh"のまま維持されます。
例2:ネストされたオブジェクトは凍結できない
一方、ネストされたオブジェクトを含むオブジェクトに対してObject.freeze()を実行しても、期待どおりの結果にはなりません。Object.freeze()は内部のネストされたオブジェクトにアクセスできないためです。
次の例では、Object.freeze()メソッドによってオブジェクト全体が凍結されているにもかかわらず、ネストされたオブジェクトのプロパティが書き換えられてしまいます。
<html>
<body>
<script>
const person = {
"name" : "Suresh",
"Country" : "India",
"Designation" : "Mayor",
"CompaniesOwned" :{
"Company1" : "Tesla",
"Company2" : "SolarCity",
"Company3" : "Spacex"
}
}
document.write("Before freeze " + " " + "Company2 is" + " "+ person.CompaniesOwned.Company2);
Object.freeze(person);
document.write("</br>");
person.CompaniesOwned.Company2 = "Neuralica";
document.write("After freeze" + " " + "Company2 is" + " "+ person.CompaniesOwned.Company2);
</script>
</body>
</html>出力結果
Before freeze Company2 is SolarCity After freeze Company2 is Neuralica
凍結済みのオブジェクトであるにもかかわらず、「Company2」の値が"SolarCity"から"Neuralica"へと変わってしまいました。これは、personオブジェクト自体は凍結されていますが、その中のCompaniesOwnedは独立した別オブジェクトであり、凍結されていないためです。Object.freeze()による凍結は「浅い(shallow)」ものだと理解しておきましょう。
解決策:再帰的に凍結する「ディープフリーズ」
ネストされた階層まで完全に不変にしたい場合は、すべての子オブジェクトに対して再帰的にObject.freeze()を適用します。よく使われる実装例が次のdeepFreeze関数です。
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).forEach((key) => {
const value = obj[key];
if (value && typeof value === "object") {
deepFreeze(value);
}
});
return Object.freeze(obj);
}
const person = deepFreeze({
"name": "Suresh",
"Country": "India",
"Designation": "Mayor",
"CompaniesOwned": {
"Company1": "Tesla",
"Company2": "SolarCity",
"Company3": "Spacex"
}
});
person.CompaniesOwned.Company2 = "Neuralica";
console.log(person.CompaniesOwned.Company2); // "SolarCity" のまま(変更されない)このようにdeepFreeze関数を使えば、ネストされた子オブジェクトも含めてオブジェクト全体を確実にイミュータブルにできます。状態の意図しない変更を防ぎたい場面では、Object.freeze()の「浅い凍結」という特性を踏まえたうえで、適切な凍結戦略を選択してください。
-
JavaScriptでURLオブジェクトを作成する方法をわかりやすく解説
JavaScriptでは、標準組み込みのURLコンストラクタを使うことで、URL文字列からURLオブジェクトを簡単に生成できます。URLオブジェクトを作成すると、プロトコル(protocol)やホスト名(host)、パス(pathname)など、URLを構成する各要素に個別にアクセスできるようになるため、URLの解析や操作がとても便利になります。 URLオブジェクトの作成例 以下は、JavaScriptでURLオブジェクトを作成し、ボタンをクリックするとその各プロパティを画面に表示するサンプルコードです。 <!DOCTYPE html> <html lang="
-
【JavaScript】配列内のネストされたオブジェクトの値を合計する方法
この記事では、JavaScriptを使って、配列内にネストされた(入れ子構造の)オブジェクトの値を合計する方法を解説します。サンプルコードでは、JSONデータの複数階層に格納された数値(costNum)を順番に取り出し、その合計をブラウザ上に表示します。 コード例 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=d