JavaScriptのObject.defineProperties
メソッドは、指定されたオブジェクトに複数の新しいプロパティを直接追加したり、既存のプロパティの属性を変更したりするために使用されます。
これは、プロパティの値
だけでなく、そのプロパティが書き込み可能か
、列挙可能か
、設定変更可能か
、あるいはゲッター/セッターを持つか
といった詳細な振る舞いを一度に制御したい場合に非常に強力です。
Object.defineProperty
が単一のプロパティを定義するのに対し、Object.defineProperties
は複数のプロパティを効率的に定義できるため、オブジェクトの初期化やメタプログラミングにおいて特に役立ちます。
この記事では、Object.defineProperties
メソッドの基本的な使い方、引数、戻り値、主要な使用例、そして使用する際の重要な注意点について解説します。
Object.definePropertiesメソッドの基本的な構文
Object.defineProperties
メソッドの基本的な構文は以下の通りです。
Object.defineProperties(obj, props)
obj
: 必須。プロパティを定義または変更したいオブジェクトです。props
: 必須。プロパティ名と、それに対応するプロパティディスクリプタを含むオブジェクトです。
下記は簡単な例です。
Object.defineProperties(obj, {
prop1: { value: 10, writable: true },
prop2: { get() { return 20; } }
});
第1引数(obj)
- 必須です。
- プロパティを定義または変更する対象のオブジェクトです。
第2引数(props)
- 必須です。
- このオブジェクトの各プロパティは、ターゲットオブジェクトに追加/変更したいプロパティの名前を表します。
- 各プロパティの値は、そのプロパティの属性を定義するプロパティディスクリプタオブジェクトです。
プロパティディスクリプタについて
プロパティディスクリプタには、主に2種類あります。
- データディスクリプタ (Data Descriptor): プロパティの値を直接持つ場合。
value
: プロパティの実際の値。デフォルトはundefined
。writable
:true
の場合、value
を変更できる。デフォルトはfalse
。enumerable
:true
の場合、for...in
ループやObject.keys()
で列挙できる。デフォルトはfalse
。configurable
:true
の場合、プロパティのディスクリプタを変更したり、プロパティを削除したりできる。デフォルトはfalse
。
- アクセサディスクリプタ (Accessor Descriptor): ゲッター/セッター関数を持つ場合。
get
: ゲッター関数。プロパティが読み取られたときに呼び出される。デフォルトはundefined
。set
: セッター関数。プロパティが設定されたときに呼び出される。デフォルトはundefined
。enumerable
:true
の場合、列挙できる。デフォルトはfalse
。configurable
:true
の場合、設定変更・削除できる。デフォルトはfalse
。
注意
データディスクリプタとアクセサディスクリプタの属性を同時に指定することはできません。
例えば、value
と get
を同じプロパティに指定するとエラーになります。
Object.definePropertiesの戻り値
変更されたオブジェクト(obj
)が返されます。
Object.definePropertiesメソッドを使ってみる
例1:複数のデータプロパティを定義する
オブジェクトに、値と属性を細かく制御した複数のプロパティを追加します。
"use strict"; // 厳格モードを有効にする
const user = {};
// userオブジェクトに複数のプロパティを定義
Object.defineProperties(user, {
// name プロパティ: 値は'Alice'、書き込み可能、列挙可能、設定変更可能
name: {
value: 'Alice',
writable: true,
enumerable: true,
configurable: true
},
// id プロパティ: 値は101、書き込み不可、列挙可能、設定変更不可
id: {
value: 101,
writable: false, // 変更不可
enumerable: true,
configurable: false // 削除不可、属性変更不可
},
// secretKey プロパティ: 値は'xyz123'、書き込み可能、列挙不可、設定変更可能
secretKey: {
value: 'xyz123',
writable: true,
enumerable: false, // for...in や Object.keys() で表示されない
configurable: true
}
});
console.log(user); // 出力: { name: 'Alice', id: 101 } (secretKeyは列挙不可なので表示されない)
user.name = 'Bob'; // writable: true なので変更可能
console.log(user.name); // 出力: Bob
try {
user.id = 102; // writable: false なのでエラー
} catch (e) {
console.error("エラー: idは変更できません。", e.message); // 出力: エラー: Cannot assign to read only property 'id' of object '#<Object>'
}
console.log(Object.keys(user)); // 出力: [ 'name', 'id' ] (secretKeyは含まれない)
// idプロパティの削除を試みる
try {
delete user.id; // configurable: false なのでエラー
} catch (e) {
console.error("エラー: idは削除できません。", e.message); // 出力: エラー: Cannot delete property 'id' of object '#<Object>'
}
user
オブジェクトを作成して、Object.defineProperties
を使用して、複数のプロパティを定義しています。
writable
が「true
」になっているname
プロパティは変更することが可能で、writable
が「false
」のid
プロパティは変更や削除ができないことが確認できます。
また、enumerable
をfalse
に設定しているためsecretKey
プロパティは、Object.keys
でキーが含まれないことが確認できました。(※ブラウザの開発ツールに依存する)
このようにname
は通常のプロパティのように振る舞い、id
は読み取り専用で削除もできないプロパティになって
secretKey
は値は設定できるものの、オブジェクトのキーとして列挙されない(隠された)プロパティになりました。
例2:ゲッターとセッターを持つプロパティを定義する
プロパティの読み書き時に特定の処理を実行したい場合に、アクセサディスクリプタを使用します。
"use strict";
const product = {
_price: 0 // 実際の価格を保持するプライベートなプロパティ
};
Object.defineProperties(product, {
// price プロパティ: ゲッターとセッターを持つ
price: {
enumerable: true,
configurable: true,
get: function() {
console.log("priceを読み込みました。");
return this._price;
},
set: function(newPrice) {
if (newPrice < 0) {
console.warn("価格は負の値に設定できません。");
return;
}
console.log(`priceを${newPrice}に設定しました。`);
this._price = newPrice;
}
},
// discountPrice プロパティ: ゲッターのみを持つ (読み取り専用の計算プロパティ)
discountPrice: {
enumerable: true,
get: function() {
// priceプロパティのゲッターを介して_priceにアクセス
return this.price * 0.9; // 10%割引
}
}
});
product.price = 100; // セッターが呼び出される
console.log(product.price); // ゲッターが呼び出される (出力: priceを読み込みました。 100)
product.price = -50; // セッター内の警告が表示され、値は変更されない
console.log(product.price); // 出力: 100
console.log(product.discountPrice); // ゲッターが呼び出される (出力: 90)
product
オブジェクトを作成して、price
プロパティとdiscountPrice
プロパティを定義しています。
price
プロパティにset/get
を書くことで、値の書き込み時と読み込み時に処理をすることができます。
discountPrice
プロパティではget
のみ用意しました。
price
プロパティは、読み書き時にログを出力し、負の値を設定できないようにバリデーションを行っています。
discountPrice
はゲッターのみを持つため、price
に基づいて計算された読み取り専用の値を提供します。
結果をみると、それぞれプロパティが呼び出された時にログが出力されていることが確認できます。
priceを100に設定しました。
priceを読み込みました。
100
価格は負の値に設定できません。
priceを読み込みました。
100
priceを読み込みました。
90
例3:既存のプロパティの属性を変更する
既に存在するプロパティの属性を変更することもできます。
ただし、configurable: false
に設定されたプロパティの属性は、後から変更できません。
"use strict";
const config = {
version: '1.0.0',
debugMode: true
};
console.log(Object.getOwnPropertyDescriptor(config, 'version'));
/*
出力例:
{
value: '1.0.0',
writable: true,
enumerable: true,
configurable: true
}
*/
// versionを読み取り専用にする
Object.defineProperties(config, {
version: {
writable: false,
configurable: false // これを設定すると、後からwritableをtrueに戻したり、プロパティを削除したりできなくなる
}
});
console.log(Object.getOwnPropertyDescriptor(config, 'version'));
/*
出力例:
{
value: '1.0.0',
writable: false,
enumerable: true, // 変更なし
configurable: false // 変更された
}
*/
try {
config.version = '1.0.1'; // writable: false なのでエラー
} catch (e) {
console.error("エラー: versionは変更できません。", e.message);
}
try {
// configurable: false になったので、このプロパティの属性を再度変更しようとするとエラー
Object.defineProperties(config, {
version: {
writable: true // エラーになる
}
});
} catch (e) {
console.error("エラー: versionの属性は変更できません。", e.message); // 出力: エラー: Cannot redefine property: version
}
この例では、version
プロパティを読み取り専用にし、さらにconfigurable: false
に設定することで、そのプロパティの属性を二度と変更できないようにしています。
Object.definePropertiesを使うときの注意点
Object.defineProperties
を使うときの注意点です。
厳格モードでのエラー
Object.defineProperties
を使用する際は、通常厳格モード ("use strict"
)で開発することをお勧めします。
非厳格モードでは、プロパティの定義に失敗してもサイレントに失敗する(エラーが発生しない)ことがあり、デバッグが困難になるためです。
既存プロパティの変更
既に存在するプロパティに対してdefineProperties
を使用すると、既存の属性が上書きされます。
特に configurable: false
に設定すると、そのプロパティは削除できなくなり、その属性(writable
, enumerable
, configurable
自体)も二度と変更できなくなるため、非常に強力かつ不可逆な操作となります。
データディスクリプタとアクセサディスクリプタの排他性
value
またはwritable
と、get
またはset
を同じプロパティディスクリプタ内で同時に指定することはできません。
どちらか一方のタイプのみを使用する必要があります。
デフォルト値
プロパティディスクリプタで明示的に指定しなかった属性(例: enumerable
や configurable
)は、デフォルト値が適用されます。
これらのデフォルト値は、通常のプロパティ代入(obj.prop = value
)で作成されるプロパティのデフォルト値とは異なる(通常は false
)ため、注意が必要です。
プロトタイプチェーン上のプロパティ
Object.defineProperties
は、オブジェクト自身のプロパティのみを操作します。
プロトタイプチェーンから継承されたプロパティは直接変更できません。
まとめ
JavaScriptのObject.defineProperties
メソッドは、オブジェクトに複数のプロパティを定義したり、既存のプロパティの属性を詳細に制御したりするための非常に強力なツールです。
読み取り専用プロパティの作成、列挙されないプロパティの定義、ゲッター/セッターによるカスタムロジックの実装など、オブジェクトの振る舞いを細かくカスタマイズしたい場合に非常に便利です。
しかし、その強力さゆえに、プロパティディスクリプタの各属性の意味、厳格モードでのエラー挙動、そして一度設定した configurable: false
が不可逆であることなど、重要な注意点を深く理解して使用することが不可欠です。
これらのポイントを踏まえ、Object.defineProperties
を効果的に活用し、JavaScriptアプリケーションの堅牢性と柔軟性を高めましょう。
コメント