導入
JavaScriptでオブジェクトを扱う際、通常はプロパティを代入したり削除したりすることは自由です。しかし、大規模なアプリケーション開発では「このプロパティは書き換えてほしくない」「列挙(ループ)に含まれないようにしたい」といった制約が必要になる場面があります。そんな時、オブジェクトのプロパティの「振る舞い」を細かく制御できるのが「プロパティ記述子(Property Descriptor)」です。
基礎知識
プロパティ記述子とは、オブジェクトの各プロパティに対して設定できる「属性」の集まりです。主に以下の2種類が存在します。
データ記述子:値(value)を持ち、書き込み可能か、削除可能かなどを制御します。
アクセサ記述子:値の代わりにゲッター(get)とセッター(set)関数を持ち、値へのアクセスをフックします。
これらの設定は、Object.defineProperty() メソッドを使用することで個別に制御可能です。
実装/解決策
プロパティ記述子を活用するには、Object.defineProperty(対象オブジェクト, プロパティ名, 記述子オブジェクト) を使用します。主なフラグは以下の通りです。
・value: プロパティの値
・writable: trueにすると値の書き換えが可能(デフォルト: false)
・enumerable: trueにするとObject.keys()やfor…inでの列挙が可能(デフォルト: false)
・configurable: trueにするとプロパティの削除や記述子の再設定が可能(デフォルト: false)
サンプルプログラム
以下のコードでは、書き換え不可で列挙もされない「定数のようなプロパティ」を定義しています。
const user = {};
// プロパティを定義
Object.defineProperty(user, 'id', {
value: 101,
writable: false, // 書き換え不可
enumerable: false, // ループで表示されない
configurable: false // 削除や再定義不可
});
// 値の書き換えを試みる(エラーにはならないが反映されない)
user.id = 999;
console.log(user.id); // 101 が出力される
// 列挙を試みる
console.log(Object.keys(user)); // [] プロパティは隠蔽されている
// 削除を試みる
delete user.id;
console.log(user.id); // 101 削除もできない
応用・注意点
現場での活用として、特によく使われるのが「イミュータブル(不変)なオブジェクトの作成」です。Object.freeze() は内部的に全てのプロパティの configurable と writable を false に設定しています。
注意点:
記述子を明示的に指定しない場合、Object.defineProperty() で作成したプロパティは、デフォルトですべて「false」になります(通常の代入操作とはデフォルト値が異なる点に注意してください)。また、strictモードを使用している場合、書き込み不可のプロパティを書き換えようとすると TypeError がスローされるため、意図しないバグを早期に発見しやすくなります。積極的に活用して、堅牢なデータ構造を作り上げましょう。