JavaScriptの「Object.seal」でオブジェクトを封印する方法とは?


JavaScriptのObject.seal()メソッドは、オブジェクトを封印するためのものです。封印とは、オブジェクトに対して以下の制限を設けることを意味します。

  • プロトタイプの再割り当てを禁止する
  • 既存プロパティの構成可能性を変更禁止する
  • 既存プロパティの列挙可能性を変更禁止する
  • 既存プロパティの削除を禁止する
  • 新しいプロパティの追加を禁止する

ただし、既存プロパティの値の書き換えは許可されます。

「Object.freeze」との違い

Object.seal()とよく似たメソッドにObject.freeze()がありますが、以下の点が異なります。

  • 言い換えると、Object.seal()はオブジェクトを「読み取り専用」にし、Object.freeze()はオブジェクトを完全に「凍結」します。
  • Object.freeze()は、Object.seal()に加えて、既存プロパティの値の書き換えも禁止します。

使い所

Object.seal()は、以下の用途でよく使用されます。

  • ライブラリやフレームワークなどで、内部で使われるオブジェクトを保護したい場合
  • オブジェクトを共有する場合で、変更されることを防ぎたい場合
  • オブジェクトの内容を変更できないようにしたい場合

const obj = {
  name: 'たろう',
  age: 30
};

Object.seal(obj);

// 以下の操作は許可されます
obj.name = 'はなこ';
console.log(obj.name); // 出力: はなこ

// 以下の操作は許可されません
obj.newProp = 'new value';
console.log(obj.newProp); // 出力: undefined
delete obj.age;
console.log(obj); // 出力: { name: 'はなこ', age: 30 }
  • 厳格モードでは、エラーメッセージが表示されますが、非厳格モードではエラーが黙殺される場合があります。
  • ただし、封印されたオブジェクトに対して、許可されていない操作を実行しようとすると、エラーが発生します。
  • Object.seal()で封印されたオブジェクトは、通常のオブジェクトと同様に操作できます。


基本的な例

const obj = {
  name: 'たろう',
  age: 30
};

Object.seal(obj);

console.log(obj); // 出力: { name: 'たろう', age: 30 }

obj.name = 'はなこ';
console.log(obj.name); // 出力: はなこ

obj.newProp = 'new value';
console.log(obj.newProp); // 出力: undefined

delete obj.age;
console.log(obj); // 出力: { name: 'はなこ', age: 30 }

プロトタイプの再割り当ての禁止

以下のコードは、オブジェクトの封印がプロトタイプの再割り当てにどのように影響を与えるのかを示しています。

const obj = {
  name: 'たろう',
  age: 30
};

Object.seal(obj);

const newProto = {
  greet: function() {
    console.log(`こんにちは、私の名前は ${this.name} です。`);
  }
};

Object.setPrototypeOf(obj, newProto);
console.log(obj.greet); // 出力: undefined

以下のコードは、オブジェクトの封印が厳格モードと非厳格モードでどのように異なる動作をするのかを示しています。

const obj = {
  name: 'たろう',
  age: 30
};

Object.seal(obj);

// 厳格モード
(() => {
  obj.newProp = 'new value';
})(); // TypeError: Cannot add property 'newProp' to an object

// 非厳格モード
(() => {
  obj.newProp = 'new value';
})(); // エラーなし


Object.freeze

利点

  • オブジェクトを完全に不変にし、意図しない変更を防ぐことができます。
  • Object.sealよりも強力で、既存プロパティの値の書き換えも禁止します。

欠点

  • Object.sealよりも柔軟性が低く、既存プロパティの値を更新する必要がある場合は使用できません。


const obj = {
  name: 'たろう',
  age: 30
};

Object.freeze(obj);

obj.name = 'はなこ'; // エラー

手動によるプロパティ設定

利点

  • Object.sealObject.freezeよりも柔軟性が高いです。
  • オブジェクトのどのプロパティを書き換え可能にするかを細かく制御できます。

欠点

  • すべてのプロパティを手動で設定する必要があるため、ミスが発生しやすくなります。
  • コードが冗長になり、メンテナンスが難しくなる可能性があります。


const obj = {
  name: 'たろう',
  age: 30
};

Object.defineProperty(obj, 'name', {
  writable: true,
  configurable: false
});

Object.defineProperty(obj, 'age', {
  writable: false,
  configurable: false
});

obj.name = 'はなこ';
console.log(obj.name); // 出力: はなこ

obj.age = 31; // エラー

イミュータブルライブラリの使用

利点

  • コードが簡潔で読みやすくなります。
  • オブジェクトの変更を安全に追跡できます。
  • イミュータブルなデータ構造を簡単に操作できます。

欠点

  • 既存のコードとの互換性が無い場合があります。
  • 新しいライブラリを習得する必要があるため、学習コストがかかります。


const { Map, Set } = require('immutable');

const obj = Map({
  name: 'たろう',
  age: 30
});

const newObj = obj.set('name', 'はなこ');
console.log(newObj.get('name')); // 出力: はなこ

// 元のオブジェクトは変更されない
console.log(obj.get('name')); // 出力: たろう

適切な代替方法の選択

どの代替方法が適切かは、状況によって異なります。以下の点を考慮して選択してください。

  • ライブラリの習得コスト
  • コードの簡潔性とメンテナンス性
  • オブジェクトの変更をどの程度許可する必要があるか