導入:なぜObject.getOwnPropertyDescriptorsが必要なのか
JavaScriptでオブジェクトをコピーする際、皆さんは普段どのように記述していますか?多くの場合、スプレッド構文(…)やObject.assign()を使用しているはずです。しかし、これらには「getter/setter」や「列挙不可なプロパティ」を正しくコピーできないという重大な欠点があります。
Object.getOwnPropertyDescriptorsは、オブジェクトが持つすべてのプロパティの属性(記述子)を一度に取得できるメソッドです。これを使うことで、オブジェクトの複製や合成を行う際に、メタデータを含めた「完全なクローン」を作成することが可能になります。
基礎知識:プロパティ記述子(Property Descriptor)とは
JavaScriptのオブジェクトの各プロパティには、値そのもの以外に「属性」が設定されています。
・value: プロパティの値
・writable: 値の書き換えが可能か
・enumerable: for…inやObject.keysで列挙可能か
・configurable: プロパティの削除や再定義が可能か
・get/set: ゲッターやセッター関数
Object.assign()などは主に「value」のみをコピーしますが、Object.getOwnPropertyDescriptorsを使えば、これらの属性設定を維持したまま別のオブジェクトへ移し替えることができます。
実装:Object.definePropertiesとの組み合わせ
Object.getOwnPropertyDescriptorsで取得した内容は、Object.definePropertiesメソッドにそのまま渡すことで、ターゲットとなるオブジェクトに対して一括でプロパティを定義できます。
サンプルプログラム:安全なオブジェクトのクローン関数
以下は、getter/setterやプロパティの属性を保持したままオブジェクトをコピーする実用的な関数の例です。
// オブジェクトの完全なクローンを作成する関数
function cloneObject(obj) {
// 1. オブジェクト自身のプロパティ記述子をすべて取得
const descriptors = Object.getOwnPropertyDescriptors(obj);
// 2. 新しい空のオブジェクトを作成し、プロパティ記述子を適用
// Object.create(Object.getPrototypeOf(obj)) を使うことで、
// プロトタイプチェーンも継承したクローンが作成可能です
const clone = Object.create(Object.getPrototypeOf(obj), descriptors);
return clone;
}
// 動作確認用データ
const source = {
name: 'Project A',
get version() { return '1.0.0'; }, // ゲッターを持つプロパティ
set version(v) { console.log('Version set to', v); }
};
const copy = cloneObject(source);
console.log(copy.name); // "Project A"
console.log(copy.version); // "1.0.0" (ゲッターも正しく動作)
// 属性の確認
console.log(Object.getOwnPropertyDescriptor(copy, 'name').enumerable); // true
応用・注意点:現場での活用と落とし穴
1. プロトタイプの扱い
前述のサンプルコードではObject.createを使用し、プロトタイプチェーンも考慮しています。スプレッド構文ではプロトタイプが失われるため、クラスのインスタンスを扱う際にはこの手法が非常に有効です。
2. シャローコピーである点
Object.getOwnPropertyDescriptorsは「浅いコピー(シャローコピー)」を行います。プロパティの値がオブジェクトや配列である場合、その参照先までコピーされるわけではない点に注意してください。ネストされた深い階層のコピーが必要な場合は、別途再帰的な処理や構造化クローン(structuredClone)との使い分けが必要です。
3. 継承プロパティは対象外
このメソッドは、あくまで「そのオブジェクト自身が持つプロパティ」のみを対象とします。プロトタイプチェーン上のプロパティは取得されないため、設計段階でどのプロパティを操作したいのかを明確にしておきましょう。
このメソッドを活用することで、ライブラリ開発や複雑な状態管理を行う際に、意図しないプロパティの欠落を防ぎ、より堅牢なコードベースを構築することができます。