Freeze(凍結)とは何か
Object.freeze(obj) は、オブジェクトを「変更できない状態」にする仕組みです。凍結すると、そのオブジェクトに対して新しいプロパティの追加、既存プロパティの削除、値の書き換え、属性の変更(writable/enumerable/configurable)などができなくなります。ここが重要です:Freezeは「完全に不変」ではなく「浅い不変」。入れ子のオブジェクトは別途凍結しない限り、まだ変更可能です。
何が禁止されるのか(凍結の効果を直感で理解)
凍結されたオブジェクトに対する「構造の変化」と「値の変更」が禁止されます。厳格モードならエラーを投げ、非厳格モードでは黙って失敗します。
"use strict";
const user = { name: "太郎", age: 20 };
Object.freeze(user);
// 追加(不可)
user.city = "東京"; // TypeError: Cannot add property city, object is not extensible
// 変更(不可)
user.age = 21; // TypeError: Cannot assign to read only property 'age'
// 削除(不可)
delete user.name; // TypeError: Cannot delete property 'name'
JavaScriptここが重要です:Freezeは「拡張不可(preventExtensions)+ 全プロパティを書き換え不可・設定変更不可」にします。結果として、見た目も中身も固定されます。
浅い凍結(ネストは別物)と安全な設計
Freezeは浅い処理です。入れ子のオブジェクトや配列は自動で凍結されません。
const config = {
version: "1.0.0",
nested: { mode: "prod" }
};
Object.freeze(config);
config.version = "2.0.0"; // 失敗(凍結済み)
// しかし内側はまだ変更可能
config.nested.mode = "dev"; // 変更される(浅い凍結なので)
console.log(config.nested.mode); // "dev"
JavaScript入れ子も含めて不変にしたいなら、再帰的に凍結(ディープフリーズ)します。
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).forEach((key) => {
const value = obj[key];
if (value && typeof value === "object") deepFreeze(value);
});
return Object.freeze(obj);
}
const cfg = deepFreeze({
version: "1.0.0",
nested: { mode: "prod" }
});
cfg.nested.mode = "dev"; // 厳格モードなら TypeError、非厳格なら無視
JavaScriptここが重要です:実務では「外側だけ凍結で十分な箇所」と「ネストも絶対不変にしたい箇所」を切り分けるのがコツです。設定や定数に近い構造はディープフリーズを検討します。
凍結されたかの確認と“解凍できない”という事実
一度 Object.freeze すると、元のオブジェクトを「解凍」する方法はありません。必要なら「新しく作る」か「凍結前のコピー」を使います。状態確認には Object.isFrozen(obj) を使います。
const obj = { a: 1 };
console.log(Object.isFrozen(obj)); // false
Object.freeze(obj);
console.log(Object.isFrozen(obj)); // true
// “解凍”は不可。代わりに新しいオブジェクトを作る
const unfrozen = { ...obj, a: 2 };
console.log(Object.isFrozen(unfrozen)); // false
JavaScriptここが重要です:Freezeは「元を守るため」の最終手段。変更したい将来があるなら、コピーを作ってから変更する設計(イミュータブル更新)に寄せます。
配列を凍結したらどうなるか(形も中身も固定)
配列の凍結は「要素の差し替え・追加・削除」も禁止します。長さも固定され、push や splice は動作しません。
"use strict";
const nums = [1, 2, 3];
Object.freeze(nums);
nums[0] = 99; // TypeError(要素変更不可)
nums.push(4); // TypeError(追加不可)
nums.splice(1, 1); // TypeError(削除不可)
console.log(nums.length); // 3(固定)
JavaScriptここが重要です:配列の中にオブジェクトがある場合、それらは浅い凍結の対象外です。要素オブジェクトまで不変にしたいなら、要素ごとに凍結します。
Freeze と他の関連APIの違い(役割の線引きを深掘り)
Object.preventExtensions(obj)
追加だけ禁止(既存の変更・削除は可能)。拡張の抑制に限る。Object.seal(obj)
追加と削除を禁止(既存の値の変更は可能)。形を固定しつつ中身は変更可能。Object.freeze(obj)
追加・削除・変更すべて禁止。最も堅い不変化。
const a = { x: 1 };
Object.preventExtensions(a);
a.y = 2; // 失敗(追加不可)
a.x = 9; // 変更は可能
const b = { x: 1 };
Object.seal(b);
delete b.x; // 失敗(削除不可)
b.x = 9; // 変更は可能
const c = { x: 1 };
Object.freeze(c);
c.x = 9; // 失敗(変更不可)
JavaScriptここが重要です:要件に応じて「形だけ固定」「中身も固定」を選びます。定数データや設定は Freeze、一部変更を許すなら Seal を選ぶとバランスが良くなります。
実践の型:定数・設定の守り方と更新の流儀
定数や設定を配布するときは、凍結して外部からの誤変更を防ぎます。更新が必要なときは「凍結したものを直接いじらず、新しいオブジェクトを作る」流儀で運用します。
const BASE_CONFIG = Object.freeze({
api: "https://example.com",
retries: 3,
options: Object.freeze({ timeout: 5000 }) // ネストも凍結
});
// 更新したいときはコピーして新規作成
const PROD_CONFIG = {
...BASE_CONFIG,
retries: 5,
options: { ...BASE_CONFIG.options, timeout: 8000 }
};
JavaScriptここが重要です:イミュータブル更新は「差分が明確」「比較が速い」「意図しない副作用がない」という利点があります。凍結+コピーの組み合わせが、壊れにくい設計の基礎になります。
まとめ
Freezeは「オブジェクトを変更不可にする」強力な防御策で、追加・削除・変更をすべて禁止します。ただし浅い凍結なので、入れ子まで不変にしたいときはディープフリーズが必要です。凍結は元に戻せないため、更新は「新しいオブジェクトを作って差し替える」発想に切り替えましょう。配列も含めて Freeze の挙動を体感し、preventExtensions や seal と使い分けることで、意図通りの不変性と安全性を手に入れられます。
