定数オブジェクトとは何か
const で宣言したオブジェクトは「変数がどのオブジェクトを指すか」が固定されます。ここが重要です:const は参照を固定するだけで、中身(プロパティや配列要素)は変更可能です。完全に不変にしたいなら“凍結”など別の手段が必要です。
const user = { name: "Alice" };
user.name = "Bob"; // OK(中身の変更)
/* user = { name: "Carol" }; */ // NG(別参照への再代入)
JavaScript参照固定と中身の変更(誤解しやすいポイント)
const は「参照が固定」
オブジェクトや配列の中身はミューテーションできます。値そのものを不変にしたいときは、非破壊更新(新インスタンスを作る)か凍結を使います。
const list = [1, 2];
list.push(3); // OK(中身の変更)
/* list = []; */ // NG(再代入)
JavaScript非破壊更新の基本形
UI状態や共有データでは“新インスタンス”で返すのが安全です。
const user = { id: 1, name: "Alice" };
const next = { ...user, name: "Bob" }; // 新インスタンス(元は保つ)
JavaScript凍結(不変化)の範囲と限界
Object.freeze は“浅い凍結”
直下プロパティを凍結します。ネストの内側はそのままです。ここが重要です:深い構造では浅い凍結だけだと変更を防げません。
const cfg = Object.freeze({ a: 1, nested: { b: 2 } });
// cfg.a = 9; // 無効(厳格モードなら例外)
cfg.nested.b = 3; // 変更できてしまう(浅い凍結)
JavaScript深い凍結が必要なら再帰
用途を絞って使いましょう(コストが高いから)。
function deepFreeze(obj) {
if (obj && typeof obj === "object") {
for (const k of Object.getOwnPropertyNames(obj)) deepFreeze(obj[k]);
return Object.freeze(obj);
}
return obj;
}
const cfg = deepFreeze({ a: 1, nested: { b: 2 } });
JavaScript破壊的メソッドの落とし穴(const でも中身は壊れる)
配列の破壊的メソッドは使えるが危険
sort, reverse, splice, push, pop, shift, unshift は“中身を直接変更”します。共有状態で使うと副作用になります。ここが重要です:UIや共有データでは非破壊版を優先。
const rows = [3, 1, 2];
// 悪い(破壊的)
rows.sort((a,b)=>a-b); // rows が書き換わる
// 良い(非破壊)
const sorted = rows.toSorted((a,b)=>a-b); // rows は保つ
const spliced = rows.toSpliced(1, 1); // 安全な削除
const reversed = rows.toReversed(); // 安全な反転
JavaScriptオブジェクトの直接変更も副作用
const でもプロパティ代入・delete は可能です。共有参照があるなら非破壊で。
const obj = { a: 1, b: 2 };
// 悪い:共有参照を壊す
obj.b = 3;
// 良い:非破壊
const next = { ...obj, b: 3 };
JavaScript設計で防ぐ(定数オブジェクトの安全運用)
“容器は const、中身は非破壊”が基本
参照のライフサイクルを安定させ、差分検知(===)やキャッシュが効くようにします。
const users = [{ id: 1, active: false }, { id: 2, active: true }];
const nextUsers = users.map(u => u.id === 1 ? { ...u, active: true } : u);
// users は保ち、nextUsers は新参照
JavaScriptモジュール定数は凍結して守る
設定や定義テーブルは凍結して“誤更新”を防止。
export const ROLES = Object.freeze({
admin: "ADMIN",
user: "USER",
});
JavaScript境界で防御的コピー
外部から受け取ったオブジェクトはコピーして内部に取り込むと安全です(参照共有の副作用を遮断)。
function register(raw) {
const input = { ...raw }; // 浅コピー
input.name = (input.name ?? "").trim();
// …
}
JavaScript浅コピーの限界と注意(ネストの参照共有)
スプレッドは1階層だけ新しくなる
ネスト内は共有のままです。必要階層だけ“段ごとにコピー”しましょう。ここが重要です:一段でも省くと副作用が漏れます。
const state = { user: { flags: { vip: false } } };
// 良い:必要階層のみコピー
const next = {
...state,
user: { ...state.user, flags: { ...state.user.flags, vip: true } }
};
JavaScriptパフォーマンスの現実(凍結・コピーのコスト)
凍結は読み取り中心の構造にだけ
大量・深い構造を頻繁に凍結するとコスト増。“設定定数”“辞書キー”など限定的に使うのが現実的です。
コピーは“必要最小限”
毎回全体をコピーすると割り当て・GCが増えます。更新が必要な部分だけ再構築し、他は参照を保つ設計にします。
例題で理解を固める
定数の設定表を安全に保つ
const TAX_RATE = 0.1; // 数値は完全不変
const CATEGORIES = Object.freeze({ // 表は浅い不変で十分なことが多い
book: "BOOK",
food: "FOOD",
misc: "MISC"
});
JavaScript共有配列を壊さずに並び替える
function sortByPrice(rows) {
return rows.toSorted((a,b)=>a.price - b.price); // 非破壊
}
JavaScript受け取った入力を安全に整形
function shapeUser(raw) {
const user = { ...raw }; // 浅コピーで境界防御
user.name = (user.name ?? "").trim();
user.age = Number.parseInt(user.age, 10) || null;
return user;
}
JavaScriptまとめ
定数オブジェクトの核心は「const は参照固定、内側は変更可能」という事実です。完全不変が必要なら凍結(浅い)や深い凍結を使い、通常は“容器は const、中身は非破壊更新”で安全と性能を両立する。配列の破壊的メソッドは避け、toSorted/toSpliced/toReversed を使う。浅コピーの限界を理解し、必要階層だけコピーする。モジュール定数は凍結、外部入力は境界でコピー。この指針に従えば、初心者でも定数オブジェクトを正しく使いこなし、意図どおりの安全な ES6+ コードを書けます。
