3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Object.freezeによる不変性設計:構造共有とミューテーション防止戦略の基礎と限界

Posted at

概要

JavaScriptではオブジェクトの変更がデフォルトで許可されているが、**意図せぬミューテーション(変更)**は予期せぬバグの温床になる。
この問題を制御するために導入されるのが、不変性(immutability)の概念

Object.freeze() を使うことでオブジェクトをミューテーション不可能にできるが、実はこれには浅い制約しか与えられない。

本稿では以下の観点から、不変性を“設計”として導入する方法を提示する:

  • Object.freeze() の正しい理解と限界
  • 深いネスト構造における凍結の再帰処理
  • 状態管理における構造共有戦略(Structural Sharing)
  • ライブラリを用いた実用的な不変性管理(Immer, Immutable.js)

Object.freeze() の基本挙動

const obj = {
  name: 'Toto'
};

Object.freeze(obj);

obj.name = 'Changed'; // ❌ 無視される(strictならエラー)
console.log(obj.name); // 'Toto'
  • ✅ プロパティの変更・追加・削除が不可
  • Object.isFrozen(obj) で凍結状態を判定可能
  • ❌ ネストされたオブジェクトは対象外(浅い凍結

浅い vs 深い凍結

const deep = {
  outer: {
    inner: 'value'
  }
};

Object.freeze(deep);

deep.outer.inner = 'changed'; // ✅ 変更される(freezeの対象外)

→ ✅ 深い構造では再帰的に凍結する必要あり


再帰的なディープフリーズの実装

function deepFreeze(obj) {
  Object.freeze(obj);
  for (const key of Object.keys(obj)) {
    const val = obj[key];
    if (typeof val === 'object' && val !== null && !Object.isFrozen(val)) {
      deepFreeze(val);
    }
  }
  return obj;
}

→ ✅ オブジェクト全体を凍結する安全な関数


不変性の設計的メリット

効果 説明
副作用の制御 状態が変更不可 → 安全な関数設計が可能
デバッグ容易 どこかで書き換えられた問題が原理的に起きない
パフォーマンス最適化 構造共有により shallow compare が可能 → React等で有効
テストの一貫性 オブジェクトが状態を変えないため、再現性の高いテスト設計が可能

状態管理における構造共有戦略(Structural Sharing)

const state1 = { a: 1, b: 2 };
const state2 = { ...state1, b: 3 };

console.log(state1 === state2); // false
console.log(state1.a === state2.a); // true(構造共有)

→ ✅ ミューテーションせず、新しいオブジェクトを作る
→ ✅ React, Redux, Vuex などでの変更検知に最適


ライブラリ活用:Immerによる不変性の“開発体験改善”

import { produce } from 'immer';

const base = { count: 0 };

const next = produce(base, draft => {
  draft.count += 1;
});

console.log(base.count); // 0
console.log(next.count); // 1
  • draft を書き換えるように書いても、元オブジェクトは不変
  • ✅ 内部的に構造比較+差分追跡 → 効率的な新状態を生成

Immutable.jsによる完全な不変コレクション

import { Map } from 'immutable';

const map1 = Map({ a: 1, b: 2 });
const map2 = map1.set('b', 3);

console.log(map1.get('b')); // 2
console.log(map2.get('b')); // 3
  • ✅ 内部的に構造共有された不変コレクション
  • ❌ 学習コストと専用メソッドの多さがデメリット

設計判断フロー

① 明示的に変更をブロックしたい? → Object.freeze

② 深い構造も含めて凍結したい? → deepFreeze関数を導入

③ 状態のミューテーションを避けつつ操作性を保ちたい? → Immer

④ 大規模な不変データ管理が必要? → Immutable.js

⑤ リアクティブな変更検知を最適化したい? → 構造共有の実装

よくある誤解

❌ Object.freeze() で全ての変更がブロックされる?

→ ❌ ネストされたオブジェクトはそのまま変更可能
→ ✅ deepFreeze() を使う or ライブラリ利用が安全


❌ Object.freeze() はセキュリティ機構?

→ ❌ いいえ、設計的ガードであり、完全な秘匿化ではない
→ プロパティは Object.getOwnPropertyDescriptors() で依然可視


結語

Object.freeze() は、単なる構文ではない。
それは「データが書き換わらないという保証」を設計上に持ち込むための契約である。

  • ミューテーションの副作用からロジックを解放し、
  • 状態の変化を構造として明確に定義し、
  • アプリケーションの一貫性と予測性を高める

不変性とは、変わらないという“前提”をコードに刻むことで、信頼できる設計を作る技法である。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?