JavaScriptではdefinePropertyメソッドによって、オブジェクトのプロパティを定義し、その属性を細やかに制御できる。
「.NET TIPS: ゲッターとセッター(プロパティ)を定義するには?[JavaScript]」では、JavaScriptでゲッター/セッターを定義する方法を幾つか紹介した。本稿では、そのうちのObject.definePropertyメソッドについて、さらに見ていくことにする。
Object.definePropertyメソッドは以下の構文で、指定したオブジェクトにプロパティを定義する。
Object.defineProperty(
オブジェクト,
プロパティ名,
プロパティディスクリプタ
)
第1引数に指定したオブジェクトに、第2引数に指定した名前のプロパティが定義される。第3引数には、そのプロパティディスクリプタ(プロパティの属性)を指定する。プロパティディスクリプタにはデータプロパティディスクリプタとアクセサプロパティディスクリプタの2種類がある(後述)。
以下に簡単なプロパティの定義例を示す。
var obj = {};
Object.defineProperty(obj, 'prop', { value: 'foo' });
// alert(obj.prop);
console.log(obj.prop);
ここではプロパティディスクリプタに「{ value: 'foo' }」と記述している。このようにvalueという名前のフィールド(あるいはwritableフィールド)を持つディスクリプタのことをデータプロパティディスクリプタ(以下、データディスクリプタ)と呼ぶ。
以下にプロパティ定義をもう1つ示す。
var obj = {};
Object.defineProperty(obj, 'prop',
{ get: function() { return 'foo'} }
);
console.log(obj.prop);
この例では、プロパティディスクリプタにgetフィールドがある。get/setフィールドを持つプロパティディスクリプタのことをアクセサプロパティディスクリプタ(以下、アクセサディスクリプタ)と呼ぶ。データディスクリプタとアクセサディスクリプタは相互排他的、つまり、value/writableフィールドを持つディスクリプタには、get/setフィールドを記述できないし、その逆も同様だ。
var obj = {};
Object.defineProperty(obj, 'prop',
{
value: 'foo',
get: function() { return 'foo'}
}
);
console.log(obj.prop);
つまり、Object.definePropertyメソッドを使って、ゲッター/セッターを必要としない単純なプロパティを定義するにはデータディスクリプタを、これらを必要とするプロパティを定義するにはアクセサディスクリプタを使用する。
なお、実際にアクセサディスクリプタを指定して、ゲッター/セッターを持つプロパティを実装するには、「.NET TIPS: ゲッターとセッター(プロパティ)を定義するには?[JavaScript]」でも行っているように、ファクトリ関数やコンストラクタ関数の内部にプライベートなローカル変数を用意してクロージャを形成することになるだろう(後述のコード例も参考のこと)。
プロパティディスクリプタには上で見たvalueフィールド、get/setフィールド以外にも幾つかのフィールドを記述できる。これらを使うことで、定義するプロパティの属性や挙動を制御可能だ。
データディスクリプタには、valueフィールドに加えて、上述のwritableフィールドとconfigurableフィールド、enumerableフィールドを記述できる。アクセサディスクリプタにはget/setフィールドに加えて、configurableフィールドとenumerableフィールドを記述できる。configurableフィールドとenumerableフィールドは2つのディスクリプタに共通のフィールドで、プロパティの属性変更やプロパティの削除が可能か(configurableフィールド)、for-inによる列挙の対象とするか(enumerableフィールド)を指定する。データディスクリプタのwritableフィールドは、valueフィールドに記述したプロパティの値が変更可能かを指定する。
データディスクリプタを使用したプロパティ定義
以下にデータディスクリプタに記述可能なフィールドをまとめる。
フィールド | 型 | 説明 |
---|---|---|
value | 任意の型 | プロパティの値。デフォルト値はundefined |
writable | boolean | trueならvalueフィールドの値を変更可能。デフォルト値はfalse(変更不可能) |
enumerable | boolean | trueならfor-inループでこのプロパティが列挙される。デフォルト値はfalse(列挙されない) |
configurable | boolean | falseならこのプロパティの削除、アクセサを持つプロパティへの変更、valueフィールド以外の値の変更が不可能。デフォルト値はfalse(フィールド値の変更やプロパティの削除が不可能) |
データディスクリプタに記述可能なフィールド |
以下にこれらのフィールドの使用例を示す。
function makePersonData1(name, tel) {
var p = {};
Object.defineProperty(p, 'name', { value: name, enumerable: true });
Object.defineProperty(p, 'tel', { value: tel, writable: true });
return p;
}
var p1 = makePersonData1('Insider.NET', '2222-2222');
// enumerableフィールドの値がtrueなのはnameプロパティのみ
// 出力結果は「name: Insider.NET」のみ
for (var item in p1) {
console.log(item + ': ' + p1[item]);
}
// nameプロパティはwritableフィールドの値がfalse
p1.name = 'Build Insider'; // strictモードではエラーになる
p1.tel = '3333-3333';
console.log(p1.name + ': ' + p1.tel); // Insider.NET: 3333-3333
ここではnameとtelの2つのプロパティを定義している。nameプロパティはwritableフィールドの値がfalse(デフォルト値)で、enumerableフィールドの値はtrueだ。telプロパティはwritableフィールドの値がtrueとなっている(enumerableフィールドの値はデフォルト値のfalse)。
そのため、for-inループでのプロパティ名の列挙では、nameプロパティだけが列挙される。また、nameプロパティの値を書き換えようとしても変更はできない。コメントにも書いた通り、strictモードではエラーが発生する。telプロパティは書き換えが可能だ。
アクセサディスクリプタを使用したプロパティ定義
以下はアクセサディスクリプタに記述可能なフィールドだ。
フィールド | 型 | 説明 |
---|---|---|
get | Function/undefined | プロパティの値の読み出し時にこの関数が呼び出される。デフォルト値はundefined(ゲッターが存在しない) |
set | Function/undefined | プロパティの値の設定時にこの関数が呼び出される。デフォルト値はundefined(セッターが存在しない) |
enumerable | boolean | trueならfor-inループでこのプロパティが列挙される。デフォルト値はfalse(列挙されない) |
configurable | boolean | falseならこのプロパティの削除、データプロパティへの変更、フィールド値の変更が不可能。デフォルト値はfalse(フィールド値の変更やプロパティの削除が不可能) |
アクセサディスクリプタに記述可能なフィールド |
以下にこれらのフィールドの使用例を示す。上のデータディスクリプタを使用したプロパティ定義と同じ意味を持つコードだ。
function makePersonData2(name, tel) {
var _name = name;
var _tel = tel;
var p = {};
Object.defineProperty(p, 'name',
{
get: () => _name,
enumerable: true
}
);
Object.defineProperty(p, 'tel',
{
get: () => _tel,
set: newtel => _tel = newtel
}
);
return p;
}
var p2 = makePersonData2('Insider.NET', '2222-2222');
// enumerableフィールドの値がtrueなのはnameプロパティのみ
// 出力結果は「name: Insider.NET」のみ
for (var item in p2) {
console.log(item + ': ' + p2[item]);
}
// nameプロパティのsetフィールドの値はundefinedなので読み出し専用
p2.name = 'Build Insider'; // strictモードではエラーとなる
p2.tel = '3333-3333';
console.log(p2.name + ': ' + p2.tel);
ここではnameとtelの2つのプロパティを定義している。nameプロパティのsetフィールドは記述していない(デフォルト値はundefined)なので、これは読み出し専用のプロパティとなる。また、enumerableフィールドの値はtrueになっている。telプロパティはget/setフィールドを記述しているので読み書きが行える。enumerableフィールドは記述していないので、その値はデフォルト値のfalseとなっている。
そのため、for-inループでのプロパティ名の列挙では、nameプロパティだけが列挙される。また、nameプロパティの値を書き換えようとしても変更はできない。コメントにも書いた通り、strictモードではエラーが発生する。telプロパティは書き換えが可能だ。
configurableフィールド
configurableフィールドは、プロパティディスクリプタの各フィールドの値を変更可能か、プロパティを削除できるかを示すものだ。以下に例を示す。
var v = {};
Object.defineProperty(v, 'foo',
{
configurable: true,
enumerable: false,
writable: false,
value: 'foo'
}
);
console.log('1st for-in loop');
for (var item in v) {
console.log(item + ': ' + v[item]);
}
Object.defineProperty(v, 'foo', { enumerable: true, writable: true });
v.foo = 'bar';
console.log('2nd for-in loop');
for (var item in v) {
console.log(item + ': ' + v[item]);
}
ここでは、オブジェクトvにプロパティfooを定義している。その値(valueフィールド)は'foo'であり、configuableフィールドはtrue(データディスクリプタのフィールドを変更可能/プロパティを削除可能)、writable/enumerableフィールドはfalseを明示的に指定している。
そのため、最初のfor-inループではfooプロパティは列挙されない。しかし、その後のObject.definePropertyメソッド呼び出しでデータディスクリプタに「{ enumerable: true, writable: true }」を指定している。これにより、fooプロパティの値を更新できるようになっている。また、次のfor-inループではfooプロパティが列挙されるようになる。
Object.definePropertyメソッドを使うと、オブジェクトにプロパティを定義できる。記述は煩雑になるが(特にアクセサディスクリプタを使用した場合)、読み出し専用のプロパティの定義や、プロパティの属性を設定が可能だ。本稿では単純な例を示したのみだが、プロパティの読み出しや設定の際にゲッター/セッターを使って何らかの処理を挟み込みたい場合に、このメソッドの利用価値があるだろう。
カテゴリ:JavaScript 処理対象:言語構文
関連TIPS:Visual Studioで静的HTMLページのJavaScriptコードをデバッグするには?
関連TIPS:strictモードとは?[JavaScript]
関連TIPS:ゲッターとセッター(プロパティ)を定義するには?[JavaScript]
Copyright© Digital Advantage Corp. All Rights Reserved.