JavaScriptのObject.getOwnPropertyDescriptorsの使い方!プロパティ属性をまとめて取得

JavaScriptのObject.getOwnPropertyDescriptorsメソッドは、指定されたオブジェクトのすべての自身のプロパティ(継承されたプロパティではない)のプロパティディスクリプタをまとめて取得するために使用されます。

プロパティディスクリプタとは、プロパティのだけでなく、そのプロパティが書き込み可能かwritable)、列挙可能かenumerable)、設定変更可能かconfigurable)、あるいはゲッター/セッターを持つかget/set)といった詳細な属性情報を含むオブジェクトです。

このメソッドは、オブジェクトのすべてのプロパティの内部的な構造を一度に調べたり、オブジェクトを完全にクローン(ディープコピーではない)したり、オブジェクトのプロパティを再定義したりする際に便利です。
Object.getOwnPropertyDescriptorが単一のプロパティのディスクリプタを返すのに対し、Object.getOwnPropertyDescriptorsはすべてのプロパティをまとめて取得できるため、オブジェクトのメタプログラミングにおいて特に役立ちます。

この記事では、Object.getOwnPropertyDescriptorsメソッドの基本的な使い方、引数、戻り値、主要な使用例、そして使用する際の重要な注意点について解説します。

Object.getOwnPropertyDescriptorsメソッドの基本的な構文

Object.getOwnPropertyDescriptorsメソッドの基本的な構文は以下の通りです。

Object.getOwnPropertyDescriptors(obj)
  • obj: 必須。プロパティディスクリプタを取得したいオブジェクトです。

引数1(obj)

  • 必須です。
  • プロパティディスクリプタを取得したいオブジェクトを指定します。
  • null または undefined を渡すと TypeError が発生します。

Object.getOwnPropertyDescriptorsの戻り値

指定されたオブジェクトのすべての自身のプロパティのプロパティディスクリプタを含む新しいオブジェクトが返されます。
元のオブジェクトは変更されません。

プロパティディスクリプタの再確認

Object.getOwnPropertyDescriptorsが返すオブジェクトの各プロパティの値は、Object.getOwnPropertyDescriptorが返すものと同じプロパティディスクリプタです。

1. データディスクリプタ (Data Descriptor)

プロパティが直接値を持つ場合。

  • value: プロパティの実際の値。
  • writable: trueの場合、valueを変更できる。
  • enumerable: trueの場合、for...inループやObject.keys()で列挙できる。
  • configurable: trueの場合、プロパティのディスクリプタを変更したり、プロパティを削除したりできる。

2. アクセサディスクリプタ (Accessor Descriptor)

プロパティがゲッター/セッター関数を持つ場合。

  • get: ゲッター関数。プロパティが読み取られたときに呼び出される。
  • set: セッター関数。プロパティが設定されたときに呼び出される。
  • enumerable: true の場合、列挙できる。
  • configurable: true の場合、設定変更・削除できる。

Object.getOwnPropertyDescriptorsメソッドを使ってみる

実際にObject.getOwnPropertyDescriptorsを使って、動作を確認してみます。

例1:オブジェクトのすべてのプロパティのディスクリプタを取得する

通常のオブジェクトのすべてのプロパティの属性を一度に確認します。

const user = {
  name: 'Alice',
  age: 30,
  city: 'New York'
};

const allDescriptors = Object.getOwnPropertyDescriptors(user);
console.log("userオブジェクトのすべてのディスクリプタ:", allDescriptors);
/*
出力:
userオブジェクトのすべてのディスクリプタ: {
  name: {
    value: 'Alice',
    writable: true,
    enumerable: true,
    configurable: true
  },
  age: {
    value: 30,
    writable: true,
    enumerable: true,
    configurable: true
  },
  city: {
    value: 'New York',
    writable: true,
    enumerable: true,
    configurable: true
  }
}
*/

userオブジェクトを作成して、次の処理でObject.getOwnPropertyDescriptorsを使って、プロパティディスクリプタを取得しています。
取得した結果をallDescriptors変数に格納した後に、コンソールログに出力しました。

結果を確認すると、userオブジェクトのすべてのプロパティ(name, age, city)について、それぞれのプロパティディスクリプタがまとめて取得されていることがわかります。

例2: Object.definePropertyで定義されたプロパティを含むオブジェクトのディスクリプタを取得する

属性が明示的に設定されたプロパティも正しく取得されます。

"use strict";

const product = {
  id: 'P001',
  _price: 100 // 内部的な価格
};

Object.defineProperty(product, 'name', {
  value: 'Wireless Mouse',
  writable: false, // 変更不可
  enumerable: true,
  configurable: true
});

Object.defineProperty(product, 'secretCode', {
  value: 'XYZ789',
  enumerable: false, // 列挙不可
  configurable: true
});

Object.defineProperty(product, 'price', {
  get: function() { return this._price; },
  set: function(newPrice) { this._price = newPrice; },
  enumerable: true,
  configurable: true
});

const allProductDescriptors = Object.getOwnPropertyDescriptors(product);
console.log("productオブジェクトのすべてのディスクリプタ:", allProductDescriptors);
/*
出力:
productオブジェクトのすべてのディスクリプタ: {
  id: {
    value: 'P001',
    writable: true,
    enumerable: true,
    configurable: true
  },
  _price: {
    value: 100,
    writable: true,
    enumerable: true,
    configurable: true
  },
  name: {
    value: 'Wireless Mouse',
    writable: false,
    enumerable: true,
    configurable: true
  },
  secretCode: {
    value: 'XYZ789',
    writable: false,
    enumerable: false,
    configurable: true
  },
  price: {
    get: [Function: get],
    set: [Function: set],
    enumerable: true,
    configurable: true
  }
}
*/

productオブジェクトを作成して、Object.definePropertyを使用して、プロパティを3つ追加しています。

結果を確認すると、通常のプロパティ(id, _price)、データディスクリプタで定義されたプロパティ(namesecretCode)、アクセサディスクリプタで定義されたプロパティ(price)のすべてについて、その詳細な属性がまとめて取得されていることがわかります。
secretCodeenumerable: falseですが、Object.getOwnPropertyDescriptorsは列挙可能かどうかに関わらず、すべての自身のプロパティのディスクリプタを返します。

例3:オブジェクトをより正確にクローンする(シャローコピーの強化版)

Object.assignはゲッター/セッターを評価して値としてコピーしますが、Object.getOwnPropertyDescriptorsを使うと、ゲッター/セッター自体を新しいオブジェクトにコピーできます。
これにより、オブジェクトのプロパティ属性を保持したままシャローコピーを作成できます。

const original = {
  a: 1,
  get b() { return this.a * 2; },
  c: { d: 3 }
};

// Object.getOwnPropertyDescriptors() で元のオブジェクトの全ディスクリプタを取得
const descriptors = Object.getOwnPropertyDescriptors(original);

// Object.create() の第2引数にディスクリプタを渡して新しいオブジェクトを作成
const clone = Object.create(Object.getPrototypeOf(original), descriptors);

console.log(clone.a); // 出力: 1
console.log(clone.b); // 出力: 2 (ゲッターが機能している)
console.log(clone.c); // 出力: { d: 3 }

// シャローコピーの挙動は変わらない
clone.c.d = 30; // ネストされたオブジェクトは参照がコピーされるため、元のオブジェクトも変更される
console.log(original.c.d); // 出力: 30 (変更された)

この方法は、Object.assign({}, original)よりも忠実なシャローコピーを作成します。
特に、ゲッターやセッターを持つプロパティをコピーしたい場合に有効です。

Object.getOwnPropertyDescriptorsを使う際の重要な注意点

Object.getOwnPropertyDescriptorsを使う際の注意点についてです。

自身のプロパティのみを対象とする

最も重要な注意点です。
プロトタイプチェーンを通じてアクセスできる継承されたプロパティのディスクリプタは含まれません。
あくまでオブジェクト自身が直接持つプロパティのみが対象です。

存在しない場合は空のオブジェクトを返す

オブジェクトに自身のプロパティが一つも存在しない場合(例: Object.create(null)で作成された空のオブジェクト)、空のオブジェクト {} が返されます。

nullまたはundefinedのオブジェクト引数

Object.getOwnPropertyDescriptors(null)Object.getOwnPropertyDescriptors(undefined) を呼び出すと TypeError が発生します。

Object.getOwnPropertyDescriptorとの違い

  • Object.getOwnPropertyDescriptor(obj, propName): 単一の指定された自身のプロパティのディスクリプタを返します。
  • Object.getOwnPropertyDescriptors(obj): オブジェクトのすべての自身のプロパティのディスクリプタを含む新しいオブジェクトを返します。複数のプロパティの情報を一度に取得したい場合に便利です。

ディープコピーではない

このメソッドは、オブジェクトのプロパティディスクリプタをコピーしますが、プロパティの値がオブジェクトである場合、そのネストされたオブジェクトは参照としてコピーされます(シャローコピー)。
完全に独立したディープコピーを作成するには、再帰的な処理やライブラリが必要です。

メタプログラミング

このメソッドは、オブジェクトのプロパティの内部的な詳細を調べ、それに基づいて動的にプロパティを操作したり、オブジェクトの挙動をカスタマイズしたりするような、より高度なシナリオ(メタプログラミング)でよく利用されます。

まとめ

JavaScriptのObject.getOwnPropertyDescriptorsメソッドは、オブジェクトのすべての自身のプロパティに関する詳細な属性情報(プロパティディスクリプタ)をまとめて取得するための強力なツールです。
オブジェクトの内部構造を包括的に把握したい場合や、プロパティ属性を保持したままオブジェクトをクローンしたい場合に非常に便利です。

自身のプロパティのみを対象とすること、null/undefinedを引数にできないこと、そしてObject.etOwnPropertyDescriptorとの使い分けを理解して使用することが不可欠です。
これらのポイントを踏まえ、Object.getOwnPropertyDescriptorsを効果的に活用し、JavaScriptアプリケーションの堅牢性と柔軟性を高めましょう。

コメント