JavaScript

JavaScriptのObject.preventExtensions():設定オブジェクトへの追加を禁止する

この記事でわかること

Object.preventExtensions()で設定名のタイプミスによるプロパティ追加を防ぐ方法を解説。引数・戻り値、ブラウザーとNode.jsでの実行手順、既存値の変更や削除が可能な点を学びます。

目的:設定名のタイプミスを見つける

JavaScriptのオブジェクトには、通常、後からプロパティを追加できます。柔軟で便利ですが、設定を書き換えるときに名前を間違えると、意図しない項目が増えてしまいます。たとえば、本来のtimeoutをtimeuotと書いても、普通のオブジェクトでは代入が成功し、肝心の待ち時間は変わりません。

Object.preventExtensions()は、対象オブジェクトへの新しい自身のプロパティの追加を禁止する静的メソッドです。必要な設定項目を先に用意してから呼び出せば、厳格モードでの書き間違いを例外として検出できます。既存の設定値は引き続き更新できるため、値を変更しながら運用する設定に向いています。

ただし、これだけで設定の内容をすべて保護できるわけではありません。この記事では、追加禁止の効き方と、その範囲を実際のコードで区別します。MDNのAPI解説

動作環境とバージョン

Object.preventExtensions()はDOMに依存しないJavaScriptの標準組み込み機能なので、ブラウザーとNode.jsの両方で利用できます。外部パッケージやネットワーク接続は不要です。

MDNでは、主要ブラウザーで2015年7月以降広く利用可能な機能とされています。本記事の実行対象は現在のChrome、Edge、Firefox、Safari、およびNode.js 22系です。古いブラウザーでは、APIの存在だけでなく、例で使うconstやletなどの構文への対応も必要になります。MDNの対応状況

APIはES5の時代から存在しますが、プリミティブを渡した場合の動作はES2015で変わっています。ES5では例外、ES2015以降では渡した値がそのまま返ります。本記事では通常のオブジェクトを対象とし、プリミティブへの呼び出しを入力検証には使いません。MDN日本語版の引数に関する説明

引数と戻り値

呼び出しの形は次のとおりです。

JavaScript
Object.preventExtensions(obj);
  • obj:拡張不可能にする対象オブジェクトです。
  • 戻り値:引数で渡したオブジェクトそのものです。
  • 処理方式:同期処理です。Promiseを返さず、awaitは不要です。

戻り値はコピーではありません。別の変数に代入しても、同じオブジェクトを参照します。そのため、対象をほかの処理と共有している場合、その処理からの追加も禁止されます。また、一度拡張不可能にした同じオブジェクトを、拡張可能へ戻すことはできません。MDN日本語版の動作説明

厳格モードを明示する理由

拡張不可能なオブジェクトへの新規プロパティ代入は、厳格モードではTypeErrorになります。一方、非厳格モードでは、通常の代入による追加が黙って無視されます。タイプミスを発見する目的なら、例外になる動作を明示的に選びましょう。MDNの追加失敗に関する説明

以下の例は関数の先頭に"use strict";を置きます。関数内の処理を厳格モードにすることで、ブラウザーのコンソールでも実行条件をそろえます。MDNの厳格モード解説

自己完結したコードと実行方法

次のコードは、既存値の更新、タイプミスによる追加失敗、既存項目の削除を順に確認します。すべての変数を関数内に置いているので、コンソールへ繰り返し貼り付けても、トップレベル変数の再宣言を避けられます。

JavaScript
(function runDemo() {
  "use strict";

  const settings = {
    timeout: 3000,
    retries: 2,
    label: "demo"
  };

  const result = Object.preventExtensions(settings);
  console.log("同じ参照: " + (result === settings));
  console.log("追加可能: " + Object.isExtensible(settings));

  settings.timeout = 5000;
  console.log("timeout: " + settings.timeout);

  try {
    settings.timeuot = 1000;
  } catch (error) {
    if (!(error instanceof TypeError)) {
      throw error;
    }
    console.log("タイプミス: " + error.name);
  }

  console.log("設定: " + JSON.stringify(settings));

  delete settings.label;
  console.log("削除後: " + JSON.stringify(settings));
})();

ブラウザーで実行する

現在のChromeやFirefoxなどで通常のWebページを開き、開発者ツールのコンソールを表示します。上のJavaScriptコード全体を入力して実行してください。開発者ツールへのコード入力は内容を確認したうえで行い、貼り付けに関する保護機能が表示された場合はブラウザーの案内に従います。

HTMLファイルやWebサーバーは必要ありません。結果はページ本体ではなく、コンソールへ表示されます。最後に表示されることがあるundefinedはコンソールによる式の評価結果で、以下のログには含めません。

Node.jsで実行する

Node.js 22系がインストールされた環境で、コード全体をprevent-extensions.cjsというファイルに保存します。拡張子を.cjsにすることで、周囲のプロジェクトのモジュール設定に左右されずに実行できます。

ターミナルで保存先のフォルダーを開き、次のコマンドを実行してください。node --versionでバージョンを確認できます。nodeが見つからない場合はNode.jsのインストールが必要です。

Shell
node --version
node prevent-extensions.cjs

ファイル名をnodeコマンドへ渡して実行する方法は、Node.js公式の実行ガイドに記載されています。

期待出力

仕様に基づく期待出力は次のとおりです。この記事のコードは実機では実行していません。

CODE
同じ参照: true
追加可能: false
timeout: 5000
タイプミス: TypeError
設定: {"timeout":5000,"retries":2,"label":"demo"}
削除後: {"timeout":5000,"retries":2}

timeoutは既存プロパティなので更新できます。timeuotは存在しないため追加が拒否されます。例外を処理しているので、その後のログと削除処理も続きます。最後の行は、追加を禁止しても、通常のオブジェクトリテラルで作った項目の削除は可能なことを示します。

追加可能かどうかの確認に使ったObject.isExtensible()は、拡張不可能なオブジェクトに対してfalseを返します。MDNの判定メソッド解説

失敗例と制約

入れ子のオブジェクトには自動で適用されない

設定の中に別のオブジェクトがある場合、その内側へは追加できます。次の例も、前節と同じ方法で単独実行できます。

JavaScript
(function runNestedDemo() {
  "use strict";

  const settings = Object.preventExtensions({
    network: { timeout: 3000 }
  });

  settings.network.retries = 2;
  console.log(JSON.stringify(settings));
})();

期待出力は次のとおりです。

CODE
{"network":{"timeout":3000,"retries":2}}

対象は外側のsettingsです。内側のnetworkにも追加を禁止したいなら、そのオブジェクトにも別途適用します。ただし、循環参照を含むデータへ無条件に再帰適用する設計は避け、どのオブジェクトを制限するかを先に決めるほうが扱いやすくなります。

値の型や範囲は検証しない

timeoutへ負数や文字列を代入しても、通常の書き込み可能なプロパティなら、このAPIは阻止しません。設定名の追加禁止と、設定値の妥当性確認は別の処理です。実用コードでは、更新関数で有限の数値か、許容範囲内かを検証する設計を併用しましょう。

削除した項目を戻せない

削除可能なプロパティは削除できますが、削除後に同じ名前で代入すると、新規プロパティの追加になるため失敗します。「項目数を固定したい」という要件には、追加禁止だけでは不足します。

また、自身のプロパティへの制約なので、継承元のプロトタイプにあるプロパティまで固定しません。対象オブジェクトのプロトタイプを別のものへ変更することは制限されますが、プロトタイプ自体の内容を凍結する機能ではありません。MDN日本語版の制約説明

エラーメッセージの全文に依存しない

例外のメッセージはJavaScriptエンジンによって異なります。主例ではerror.nameだけを出力し、想定したTypeError以外は再送出しています。エラーを何でも握りつぶすと、別の不具合を見逃すためです。

なお、Object.defineProperty()による新規プロパティの定義は、厳格モードかどうかにかかわらず例外になります。MDNのエラー解説

活用事例と導入時の判断

向いているのは、初期化時に設定名が決まり、運用中は値だけを変更する用途です。たとえば、通信処理の待ち時間や再試行回数、画面表示の設定などで、誤った名前への代入を早く発見できます。

導入するときは、必要な項目をすべて作成してから追加を禁止します。後から設定項目を増やすライブラリーや、補助情報を付け足す処理へ同じオブジェクトを渡す場合は、追加失敗が新たに発生するため、受け渡し先の動作を確認してください。

要件を改善するなら、「追加だけを禁止したいのか」「削除も禁止したいのか」「値の変更も禁止したいのか」を先に分けるのが有効です。削除も禁止するならObject.seal()、通常のデータプロパティの値の変更も禁止するならObject.freeze()が比較対象になります。どちらも入れ子全体を自動で保護するものではありません。

まずは自分で所有する小さな設定オブジェクトに適用し、タイプミスの検出と正常な更新を確認しましょう。既存の共有オブジェクトへ一括適用するより、必要な制約を判断しやすくなります。

公式参考資料

PREFERRED SOURCES

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

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

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

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

y.
WRITTEN BY

y_ymo10

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

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