Code Fix

上級

Object.freeze()は1階層しか凍結しない(浅い凍結)

Object.freeze()はオブジェクト自身の直下のプロパティの再代入・追加・削除だけを禁止します。プロパティの値がオブジェクトである場合、そのネストしたオブジェクトは凍結されず、自由に変更できてしまいます。

なぜエラーが出ないのか

出力: true
(エラーなし)
JavaScriptは何も報告しません。文法として正しいためです
出力: true
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  function isDeeplyFrozen(obj) {
 2    return Object.isFrozen(obj) && Object.isFrozen(config     );
                                                          ^
 3  }
 4  const config = Object.freeze({ name: "app", meta: { version: 1 } });
 5  console.log(isDeeplyFrozen(config));
出力: true期待: false (エラーにはなりません)

Object.freezeは直下のプロパティしか凍結しません。configは凍結されていても、ネストしたmetaは凍結されていないため、isFrozenチェックがすり抜けます。

直し方: config を config.meta にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  function isDeeplyFrozen(obj) {
 2    return Object.isFrozen(obj) && Object.isFrozen(settings       );
                                                            ^
 3  }
 4  const settings = Object.freeze({ theme: "dark", limits: { max: 10 } });
 5  console.log(isDeeplyFrozen(settings));
出力: true期待: false (エラーにはなりません)

settings自体は凍結されていますが、ネストしたlimitsオブジェクトは凍結の対象外です。

直し方: settings を settings.limits にします。

この問題を解いてみる →

パターン3

 1  function isDeeplyFrozen(obj) {
 2    return Object.isFrozen(obj) && Object.isFrozen(profile       );
                                                            ^
 3  }
 4  const profile = Object.freeze({ name: "Al", social: { twitter: "@al" } });
 5  console.log(isDeeplyFrozen(profile));
出力: true期待: false (エラーにはなりません)

profileは凍結されていても、ネストしたsocialオブジェクトは別物として扱われ、凍結されていません。

直し方: profile を profile.social にします。

この問題を解いてみる →

パターン4

 1  function isDeeplyFrozen(obj) {
 2    return Object.isFrozen(obj) && Object.isFrozen(device      );
                                                           ^
 3  }
 4  const device = Object.freeze({ name: "phone", specs: { ram: 8 } });
 5  console.log(isDeeplyFrozen(device));
出力: true期待: false (エラーにはなりません)

deviceは凍結されていても、ネストしたspecsオブジェクトは凍結の対象外です。

直し方: device を device.specs にします。

この問題を解いてみる →

パターン5

 1  function isDeeplyFrozen(obj) {
 2    return Object.isFrozen(obj) && Object.isFrozen(book       );
                                                          ^
 3  }
 4  const book = Object.freeze({ title: "JS", author: { name: "Al" } });
 5  console.log(isDeeplyFrozen(book));
出力: true期待: false (エラーにはなりません)

bookは凍結されていても、ネストしたauthorオブジェクトは別物として扱われ凍結されていません。

直し方: book を book.author にします。

この問題を解いてみる →

よくある誤解

「freezeしたオブジェクトはまるごと不変になるはず」という思い込みは誤りです。凍結されるのは最上位の階層だけで、ネストした値まで凍結したい場合は再帰的に凍結する処理が別途必要です。

実務での勘所

凍結されたオブジェクトへの書き込みを試みたとき、通常のスクリプト(非strict mode)では何のエラーも出さずに黙って失敗します。書き込んだつもりのコードがあっても、実際には値は変わっていません。一方、ESモジュール(import/exportを使うファイル)やclass構文の中は常にstrict modeで実行されるため、同じ書き込みがTypeErrorとして例外を投げてくれます。凍結の効果を確認したいときは、strict modeで実行してエラーが出るかどうかを見るほうが、値が変わっていないことを毎回確認するより気づきやすい方法です。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)