プロパティディスクリプターには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; // 2
configurable
を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: ...}