2022年10月18日

プロパティフラグとディスクリプタ

ご存知のように、オブジェクトはプロパティを格納できます。

これまで、プロパティは私たちにとって単純な「キーと値」のペアでした。しかし、オブジェクトのプロパティは実際にはより柔軟で強力なものです。

この章では追加の設定オプションについて学び、次の章ではそれらを不可視的にゲッター/セッター関数に変換する方法を見ていきます。

プロパティフラグ

オブジェクトのプロパティは、valueに加えて、3つの特別な属性(いわゆる「フラグ」)を持っています。

  • writabletrueの場合、値を変更できます。そうでない場合は読み取り専用です。
  • enumerabletrueの場合、ループにリストされます。そうでない場合はリストされません。
  • configurabletrueの場合、プロパティを削除でき、これらの属性を変更できます。そうでない場合はできません。

私たちはまだそれらを見ていませんでした。なぜなら、一般的にそれらは表示されないからです。「通常の方法」でプロパティを作成すると、すべてがtrueになります。しかし、いつでも変更することもできます。

まず、これらのフラグを取得する方法を見てみましょう。

Object.getOwnPropertyDescriptorメソッドを使用すると、プロパティに関する完全な情報を照会できます。

構文は次のとおりです。

let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
obj
情報を取得するオブジェクト。
propertyName
プロパティの名前。

返される値はいわゆる「プロパティディスクリプタ」オブジェクトです。値とすべてのフラグが含まれています。

例えば

let user = {
  name: "John"
};

let descriptor = Object.getOwnPropertyDescriptor(user, 'name');

alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
{
  "value": "John",
  "writable": true,
  "enumerable": true,
  "configurable": true
}
*/

フラグを変更するには、Object.definePropertyを使用できます。

構文は次のとおりです。

Object.defineProperty(obj, propertyName, descriptor)
objpropertyName
ディスクリプタを適用するオブジェクトとそのプロパティ。
descriptor
適用するプロパティディスクリプタオブジェクト。

プロパティが存在する場合、`defineProperty`はそのフラグを更新します. そうでない場合、指定された値とフラグでプロパティを作成します。その場合、フラグが指定されていない場合は、`false`とみなされます.

たとえば、ここでは、すべてのフラグが偽であるプロパティ`name`が作成されます

let user = {};

Object.defineProperty(user, "name", {
  value: "John"
});

let descriptor = Object.getOwnPropertyDescriptor(user, 'name');

alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
  "value": "John",
  "writable": false,
  "enumerable": false,
  "configurable": false
}
 */

上記の「通常作成された」`user.name`と比較してください。すべてのフラグが偽です。それが望まないものであれば、`descriptor`で`true`に設定する方が良いでしょう。

それでは、フラグの効果を例で見てみましょう。

書き込み不可

`writable`フラグを変更して、`user.name`を書き込み不可(再割り当て不可)にしましょう

let user = {
  name: "John"
};

Object.defineProperty(user, "name", {
  writable: false
});

user.name = "Pete"; // Error: Cannot assign to read only property 'name'

これで、独自の`defineProperty`を適用して上書きしない限り、誰もユーザーの名前を変更できません.

エラーは厳格モードでのみ発生します

非厳格モードでは、書き込み不可のプロパティなどに書き込んでもエラーは発生しません。しかし、操作は依然として成功しません。フラグに違反するアクションは、非厳格モードでは単に無視されます。

これは同じ例ですが、プロパティは最初から作成されています

let user = { };

Object.defineProperty(user, "name", {
  value: "John",
  // for new properties we need to explicitly list what's true
  enumerable: true,
  configurable: true
});

alert(user.name); // John
user.name = "Pete"; // Error

列挙不可

それでは、`user`にカスタム`toString`を追加しましょう。

通常、オブジェクトの組み込み`toString`は列挙不可であり、`for..in`には表示されません。しかし、独自の`toString`を追加すると、デフォルトでは次のように`for..in`に表示されます

let user = {
  name: "John",
  toString() {
    return this.name;
  }
};

// By default, both our properties are listed:
for (let key in user) alert(key); // name, toString

それが気に入らない場合は、`enumerable:false`を設定できます。そうすれば、組み込みのものと同様に、`for..in`ループには表示されません

let user = {
  name: "John",
  toString() {
    return this.name;
  }
};

Object.defineProperty(user, "toString", {
  enumerable: false
});

// Now our toString disappears:
for (let key in user) alert(key); // name

列挙不可のプロパティも`Object.keys`から除外されます

alert(Object.keys(user)); // name

設定不可

設定不可フラグ(`configurable:false`)は、組み込みオブジェクトとプロパティに対して事前に設定されている場合があります。

設定不可のプロパティは削除できず、その属性を変更できません。

たとえば、`Math.PI`は書き込み不可、列挙不可、設定不可です

let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');

alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
  "value": 3.141592653589793,
  "writable": false,
  "enumerable": false,
  "configurable": false
}
*/

そのため、プログラマーは`Math.PI`の値を変更したり、上書きしたりできません。

Math.PI = 3; // Error, because it has writable: false

// delete Math.PI won't work either

また、`Math.PI`を再び`writable`に変更することもできません

// Error, because of configurable: false
Object.defineProperty(Math, "PI", { writable: true });

`Math.PI`に対してできることは何もありません。

プロパティを設定不可にすることは一方通行です。`defineProperty`で元に戻すことはできません。

注意:`configurable: false`はプロパティフラグの変更と削除を防止しますが、値の変更は許可します.

ここでは`user.name`は設定不可ですが、それでも変更できます(書き込み可能なので)

let user = {
  name: "John"
};

Object.defineProperty(user, "name", {
  configurable: false
});

user.name = "Pete"; // works fine
delete user.name; // Error

そしてここでは、組み込みの`Math.PI`のように、`user.name`を「永遠に封印された」定数にします

let user = {
  name: "John"
};

Object.defineProperty(user, "name", {
  writable: false,
  configurable: false
});

// won't be able to change user.name or its flags
// all this won't work:
user.name = "Pete";
delete user.name;
Object.defineProperty(user, "name", { value: "Pete" });
可能な属性の変更は1つだけです:writable true → false

フラグの変更について、小さな例外があります.

設定不可のプロパティに対して`writable: true`を`false`に変更して、値の変更を防止できます(別の保護層を追加するため)。ただし、逆はできません。

Object.defineProperties

多くのプロパティを一度に定義できるメソッドObject.defineProperties(obj, descriptors)があります。

構文は次のとおりです。

Object.defineProperties(obj, {
  prop1: descriptor1,
  prop2: descriptor2
  // ...
});

例えば

Object.defineProperties(user, {
  name: { value: "John", writable: false },
  surname: { value: "Smith", writable: false },
  // ...
});

そのため、多くのプロパティを一度に設定できます。

Object.getOwnPropertyDescriptors

すべてのプロパティディスクリプタを一度に取得するには、Object.getOwnPropertyDescriptors(obj)メソッドを使用できます。

`Object.defineProperties`と一緒に使用すると、オブジェクトをクローンする「フラグ対応」の方法として使用できます

let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));

通常、オブジェクトをクローンするときは、次のように代入を使用してプロパティをコピーします

for (let key in user) {
  clone[key] = user[key]
}

…しかし、これはフラグをコピーしません。「より良い」クローンが必要な場合は、`Object.defineProperties`が推奨されます.

もう1つの違いは、`for..in`はシンボリックプロパティと列挙不可プロパティを無視しますが、`Object.getOwnPropertyDescriptors`はシンボリックプロパティと列挙不可プロパティを含む*すべて*のプロパティディスクリプタを返します.

オブジェクトをグローバルに封印する

プロパティディスクリプタは、個々のプロパティのレベルで機能します.

*オブジェクト全体*へのアクセスを制限するメソッドもあります

Object.preventExtensions(obj)
オブジェクトに新しいプロパティを追加することを禁止します.
Object.seal(obj)
プロパティの追加/削除を禁止します. 既存のプロパティすべてに`configurable: false`を設定します.
Object.freeze(obj)
プロパティの追加/削除/変更を禁止します. 既存のプロパティすべてに`configurable: false, writable: false`を設定します.

また、それらのテストもあります

Object.isExtensible(obj)
プロパティの追加が禁止されている場合は`false`を、そうでない場合は`true`を返します.
Object.isSealed(obj)
プロパティの追加/削除が禁止されており、既存のプロパティすべてに`configurable: false`が設定されている場合は`true`を返します.
Object.isFrozen(obj)
プロパティの追加/削除/変更が禁止されており、現在のプロパティすべてに`configurable: false, writable: false`が設定されている場合は`true`を返します.

これらのメソッドは実際にはめったに使用されません.

チュートリアルマップ