プロパティディスクリプターには2つのタイプがあります。
データディスクリプター
アクセサディスクリプター
データディスクリプター
以下のようなオブジェクトを定義する場合で考えます。
const object = {
foo: 1,
};この定義方法は以下と同等です。
const object = {};
Object.defineProperty(object, 'foo', {
value: 1,
writable: true,
enumerable: true,
configurable: true,
});オブジェクトの各プロパティは値の他にwritableやenumerable、configurableなどの詳細情報を持ちます。
writable
writableではそのキーの値を更新できるかを決めます。trueだと変更可能でObject#definePropertyで値を渡さなかった時はfalseが設定されます。また、falseの時に値を更新しようとするとTypeErrorが起きます。
const object = {};
Object.defineProperty(object, 'a', {
value: 1,
writable: false,
});
object.a = 2;
// TypeError
// Cannot assign to read only property 'a' of object '#あるオブジェクトのすべてのプロパティのwritableをfalseにしたい時、Object#freezeメソッドが使えます。これはconfigurableもfalseに設定し、オブジェクトが新しいプロパティを追加できなくするので注意が要ります。
Object.freeze(object);
// Object.isFrozen(object);enumerable
enumerableではキーを巡回する時に、そのキーも巡回対象に含めるかを決めます。trueだとObject#keysの結果の配列に含まれたり、for-in構文で回るキーに含まれたりします。
Object#definePropertyで値を渡さなかった時はfalseが設定されます。
const object = {a: 1};
Object.defineProperty(object, 'b', {
value: 2,
enumerable: true,
});
console.log(Object.keys(object));
// ["a", "b"]
for (const key in object) {
console.log(key);
}
// "a"
// "b"configurable
configurableではそのディスクリプターを更新できるかを決めます。Object#definePropertyで値を渡さなかった時はfalseが設定されます。また、falseの時に値を更新しようとするとTypeErrorが起きます。
const object = {a: 1};
Object.defineProperty(object, 'b', {
value: 2,
configurable: false,
});
Object.defineProperty(object, 'b', {
value: 3,
});
// TypeError
// Cannot redefine property: b更新できなくしたプロパティは削除などもできなくなります。
delete object.b;
object.b; // 2configurableをtrueからfalseに更新したい場合は、同じプロパティに対して再度definePropertyを使います。
Object.defineProperty(object, 'b', {
value: 2,
configurable: false,
});またすべてのconfigurableをfalseにしたい場合は、Object#sealやObject#freezeメソッドが使えます。Object#freezeはwritableもfalseにします。
注意点としてこれらのメソッドを適用したオブジェクトはプロパティの追加もできなくなります。
Object.seal(object);
// Object.isSealed(object);アクセサディスクリプター
データディスクリプターのvalueとwritableの代わりにgetとsetの項目が含まれます。このgetとsetはそれぞれゲッターとセッターとなる関数であり、アクセサディスクリプターはゲッターとセッターに関する詳細設定を行います。
以下のようなオブジェクトを定義する場合で考えます。
const object = {
get foo() {
return this._foo;
},
set foo(value) {
this._foo = value;
},
};この定義方法は以下と同等です。
const object = {};
Object.defineProperty(object, 'foo', {
get() {
return this._foo;
},
set(value) {
this._foo = value;
},
enumerable: true,
configurable: true,
});この中でenumerableとconfigurableはデータディスクリプターのそれをまったく同じ使い方をします。
セッターを設定しないと、それを使おうとした時にTypeErrorが起きます。
const object = {};
Object.defineProperty(object, 'foo', {
get() {
return this._foo;
},
// `set: undefined` と同じ
enumerable: true,
configurable: true,
});
object.foo = '...';
// TypeError
// Cannot set property foo of #対してゲッターは設定しないとしてもundefinedとして使えます。
const object = {};
Object.defineProperty(object, 'foo', {
// `get: undefined` と同じ
set(value) {
this._foo = value;
},
enumerable: true,
configurable: true,
});
object.foo // undefinedディスクリプターの取得
1つだけならObject#getOwnPropertyDescriptor、すべてならObject#getOwnPropertyDescriptorsで取得できます。ちなみにObject#getOwnPropertyDescriptorsで取得する値に関してenumerableの影響は受けず常に取得します。
Object#getOwnPropertyDescriptor
第1引数にオブジェクト、第2引数に取得したいキーを渡します。
Object.getOwnPropertyDescriptor(
object,
'foo',
);
// {value: ...}Object#getOwnPropertyDescriptors
第1引数にオブジェクトを渡すだけです。各値がディスクリプターとなったオブジェクトが帰ります。
Object.getOwnPropertyDescriptors(
object,
);
// {foo: ...}