JavaScript

JavaScriptのObject.getOwnPropertyDescriptors():プロパティの設定をまとめて調べる

この記事でわかること

Object.getOwnPropertyDescriptors()で書き換え可否やgetterの有無を調べる方法を解説。ブラウザーとNode.jsの実行手順、期待出力、非列挙プロパティや参照共有の注意点まで学べます。

オブジェクトに値を代入しても変わらない、一覧に出てこない項目がある、値を読んだだけで処理が走る。こうした現象を調べるには、値だけでなくプロパティの設定を見る必要があります。

Object.getOwnPropertyDescriptors()は、オブジェクト自身が持つプロパティの設定をまとめて取得するAPIです。この記事では、設定オブジェクトを調査する例を通して、書き換え可否とgetterの有無を確認します。

何を取得するAPIなのか

プロパティには、値に加えて「代入できるか」「列挙の対象になるか」といった属性があります。その属性をまとめたオブジェクトをプロパティ記述子と呼びます。

Object.getOwnPropertyDescriptors()は、元のキーに対応する記述子を格納した新しいオブジェクトを返します。引数は調査対象のobjの1つだけで、コールバックやオプションはありません。対象に自身のプロパティがなければ空のオブジェクトを返します。MDNのAPIリファレンス

JavaScript
const item = { price: 1200 };
const descriptors = Object.getOwnPropertyDescriptors(item);
console.log(descriptors.price.value); // 1200
console.log(descriptors.price.writable); // true

メソッド名のOwnは「自身が直接持つ」という意味です。継承したプロパティは対象外ですが、自身の非列挙プロパティとSymbolキーは取得対象です。この範囲はMDNの列挙可能性と所有権の説明で確認できます。

動作環境と実行方法

2026年9月28日時点で確認したMDNでは、このAPIは2017年4月から主要ブラウザーで広く利用可能とされています。現在のChrome、Edge、Firefox、Safariで利用できます。ただし、この年月は古いブラウザーすべての対応を意味しません。古い組み込みブラウザーなどでは、後述する機能検出を行ってください。

Node.jsでも動作します。Node.jsの公式告知では、7.0.0で導入したV8 5.4の機能として、このES2017のAPIが紹介されています。7.0.0は導入時期を示す番号です。これから試す環境にはNode.js 24系など、サポート対象の環境を使います。Node.jsのV8 5.4導入告知

ブラウザーで試す

通常のWebページで開発者ツールを開き、Consoleを選択します。次の節のJavaScriptコード全体を入力して実行してください。ページの要素や外部ライブラリを使わないため、HTMLファイルの準備は不要です。

Node.jsで試す

次の節のコードをUTF-8のinspect-properties.jsとして保存します。そのファイルを保存したフォルダーでターミナルを開き、以下を実行してください。パッケージのインストールやモジュール設定は不要です。

Shell
node --version
node inspect-properties.js

この記事の期待出力は仕様に基づいて点検したもので、実機で実行した結果ではありません。

自己完結したコードで設定を調べる

設定オブジェクトsettingsに、通常の値、読み取り専用の項目、getterを用意します。準備に使うObject.defineProperty()は、プロパティの属性を指定するための補助です。

JavaScript
(() => {
  "use strict";

  if (typeof Object.getOwnPropertyDescriptors !== "function") {
    console.log("この環境は対応していません");
    return;
  }

  let getterCalls = 0;
  const settings = {
    mode: "preview",
    get label() {
      getterCalls += 1;
      return "mode=" + this.mode;
    }
  };

  Object.defineProperty(settings, "buildId", {
    value: "build-a",
    writable: false,
    enumerable: false,
    configurable: false
  });

  const descriptors = Object.getOwnPropertyDescriptors(settings);

  console.log("modeの値: " + descriptors.mode.value);
  console.log("modeは代入可能: " + descriptors.mode.writable);
  console.log("buildIdは列挙可能: " + descriptors.buildId.enumerable);
  console.log("labelはgetter: " + (typeof descriptors.label.get === "function"));
  console.log("調査後のgetter実行回数: " + getterCalls);

  try {
    settings.buildId = "build-b";
  } catch (error) {
    console.log("代入失敗: " + error.name);
  }

  descriptors.mode.value = "production";
  console.log("記述子変更後のmode: " + settings.mode);
  console.log("labelの値: " + settings.label);
  console.log("値の取得後のgetter実行回数: " + getterCalls);
})();

対応環境での期待出力は次のとおりです。Consoleが最後に表示する評価結果のundefinedは、このログとは別です。

CODE
modeの値: preview
modeは代入可能: true
buildIdは列挙可能: false
labelはgetter: true
調査後のgetter実行回数: 0
代入失敗: TypeError
記述子変更後のmode: preview
labelの値: mode=preview
値の取得後のgetter実行回数: 1

buildIdは列挙対象外でも、返されたdescriptorsに含まれています。また、調査した時点ではlabelのgetterは実行されず、最後にsettings.labelを読んだときだけ実行されます。

コード全体を即時実行関数で囲んでいるため、Consoleで繰り返し実行しても、前回の変数宣言と衝突しません。

戻り値の記述子を読み解く

値を持つデータプロパティ

modeやbuildIdの記述子には、次の項目があります。

  • valueは保存されている値です。
  • writableは代入による値の変更が可能かを表します。
  • enumerableは列挙対象になるかを表します。
  • configurableは削除や属性の再設定に関わる設定です。

今回のbuildIdはwritable: falseなので、strictモードでの代入がTypeErrorになります。configurable: falseでも、データプロパティがまだwritable: trueなら値の変更など一部の操作は可能です。「すべて変更禁止」と一括りにしないでください。属性の意味と例外はMDNのプロパティ定義の説明に整理されています。

getterを持つアクセサープロパティ

labelの記述子にはgetとsetがあり、valueとwritableはありません。今回のgetは関数、setはundefinedです。

そのため、descriptors.label.valueを読んでも計算結果は取れません。調査で知りたいのがgetterの存在なら、サンプルのようにgetの型を確認します。計算結果が必要なときに、元のオブジェクトのlabelへアクセスしてください。

失敗しやすい使い方と制約

記述子の書き換えで元の設定は変わらない

取得した記述子は、元のプロパティへの設定画面ではありません。サンプルではdescriptors.mode.valueを変更しても、settings.modeはpreviewのままです。取得した記述子の変更が元の属性に影響しないことは、MDNの個別記述子の説明でも明記されています。

ただし、記述子のvalueがオブジェクトなら、その参照は共有されます。次の例は前のコードと独立して実行できます。

JavaScript
(() => {
  const settings = { ui: { theme: "light" } };
  const descriptors = Object.getOwnPropertyDescriptors(settings);
  descriptors.ui.value.theme = "dark";
  console.log(settings.ui.theme); // dark
})();

この操作は記述子のvalueを置き換えるのではなく、共有しているオブジェクトの中身を変えています。調査用途では、取得結果から入れ子のデータを書き換えないようにします。

不正な対象と例外を扱う

nullとundefinedを渡すとTypeErrorになります。外部から受け取った値を調査する場合、次のように呼び出しを囲むと、失敗後の処理を続けられます。

JavaScript
(() => {
  try {
    Object.getOwnPropertyDescriptors(null);
  } catch (error) {
    console.log("調査失敗: " + error.name);
  }
})();

期待出力は調査失敗: TypeErrorです。このAPIは同期処理なので、awaitやPromiseの待機は不要です。

また、通常のオブジェクトのgetterを実行しないことと、あらゆる対象で副作用がないことは別です。対象がProxyなら、記述子取得に対応するトラップが動作する可能性があります。外部のオブジェクトを調べる汎用ツールでは、例外が発生し得る処理として扱います。MDNのProxyの記述子取得トラップ

実務での活用事例

設定を更新できない不具合では、まず対象の記述子を取り、データプロパティならwritable、アクセサープロパティならsetを確認すると原因を絞れます。値だけをログに出すより、更新できない理由を説明しやすくなります。

一覧に出ない項目の調査にも使えます。直接アクセスできるのに列挙結果に現れないなら、enumerableを確認します。ただし、非列挙という設定は秘密情報の保護ではありません。この記事の例のように、専用のAPIから取得できます。

開発用の診断表示では、getterを実行せずに「計算される項目が存在する」と表示する用途も考えられます。まず自分で作った通常のオブジェクトを対象にし、取得した記述子を読み取り専用の調査結果として扱うと、導入しやすいでしょう。

公式参考資料の使い分け

APIリファレンスでは引数と戻り値、列挙可能性のガイドでは取得範囲を確認できます。属性を読み解く際はプロパティ定義の説明を、導入時期はNode.jsの公式告知を参照してください。本文中のリンクはいずれも、執筆時に確認した公式資料です。

PREFERRED SOURCES

Googleの優先するニュース提供元にSEの部屋を追加

Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。

Googleの設定画面で確認する 新しいタブで開きます

候補にSEの部屋が表示されない場合は、まだ追加できません。

y.
WRITTEN BY

y_ymo10

SEの部屋で、HTML・CSSからJavaScript・React・Next.jsまで、Web制作の開発ノートを公開しています。

ほかの開発ノートを読む →