【JavaScript】Objectのプロパティ定義方法

# はじめに

JavaScriptのプロパティ定義には以下のような方法があります。

```js
const o1 = {
  prop: 'hoge',
  propFn: () => 10000,
};

const o2 = new Object();
o2.prop = 'hoge';
o2['propFn'] = () => 10000;
```

そしてこれら定義されたメンバは[for...in](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for...in)によって列挙することができます。

```js
for (const property in o1) {
  console.log(`${property}: ${o1[property]}`);
}

// expected output:
// prop: hoge
// propFn: () => 10000

for (const property in o2) {
  console.log(`${property}: ${o2[property]}`);
}

// expected output:
// prop: hoge
// propFn: () => 10000
```

ふー！　これでObjectのプロパティの定義方法と一覧を取得できることがわかりました！　それでは。

……とはいかないですよね。そもそもここはまだ「はじめに」です。
[for...in](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for...in)にはのっけから気になる記述があります。

> for...in 文は、キーが文字列であるオブジェクトの列挙可能プロパティすべてに対して、継承された列挙可能プロパティも含めて反復処理を行います (Symbol がキーになったものは無視します)

オブジェクトの **列挙可能** プロパティすべて???

# 列挙可能・列挙不可能

『列挙可能プロパティ』 はリンクになっているため、リンク先を見てみます。すると[プロパティの列挙可能性と所有権](https://developer.mozilla.org/ja/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)にたどり着きます。
検出の項目には[Object.prototype.propertyIsEnumerable()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable)があるためとりあえず見てみましょう。

このページに書かれていることを引用すると以下の通りです。

- コード

```js
obj.propertyIsEnumerable(
  /** 調べたいプロパティの名前 */
) => /** 指定されたプロパティが列挙可能であり、かつオブジェクト自身のプロパティであるかどうか */
```

- 解説

> すべてのオブジェクトは `propertyIsEnumerable` メソッドを持っています。このメソッドはあるオブジェクトのプロパティが、プロトタイプチェーンを通じて継承されたプロパティを除いて `for...in` ループで列挙可能かどうかを特定することができます。もしオブジェクトが指定されたプロパティを持っていない場合、このメソッドは`false`を返します。

なるほど。 **列挙されない** プロパティを定義できるんですね。これは驚いた。
でも確かに、Objectにはパッと思いつくだけでも[Object.prototype.toString()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/toString)や[Object.prototype.constructor
](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor)が生えているのに列挙されていませんね。気づかなかっただけでずっとそこにいたわけです。
でもどうやって定義するのでしょうか？

# 列挙不可能なプロパティを定義

元のページに戻ると以下の記述があります。

> `Object.defineProperty`で追加したプロパティはデフォルトで列挙可能性が`false`になります

[Object.defineProperty()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)でもプロパティを追加できるのか。
見てみましょう。使い方は以下の通り。

```js
/**
 * @param obj プロパティを定義するオブジェクト
 * @param prop 定義または変更するプロパティの名前またはSymbol
 * @param descriptor 定義または変更するプロパティの記述子
 * @return この関数に渡されたオブジェクト
 */
Object.defineProperty(obj, prop, descriptor)
```

`obj` は対象のオブジェクト、 `prop` はキーに該当するもので間違い無いでしょう。では `descriptor` は？
それは[解説](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#%E8%A7%A3%E8%AA%AC)に書かれています。
そのまま引用します。

- `configurable`

`true`である場合のみ、この種の記述子を変更することや、対応するオブジェクトからプロパティを削除することができます。 既定値は`false`です。

- `enumerable`

`true`である場合のみ、このプロパティは対応するオブジェクトでのプロパティ列挙に現れます。 既定値は`false`です。

データ記述子は以下のオプションキーも持ちます。

- `value`

プロパティに関連づけられた値です。有効なJavaScriptの値(`number`, `object`, `function`など) である必要があります。
既定値は`undefined`です。

- `writable`

`true`である場合のみ、プロパティに関連づけられた値は代入演算子で変更することができます。既定値は`false`です。

なるほどなるほど。列挙する・しないは`enumerable`で決めることができるんですね。
また加えてdeleteできるかどうかや書き換えることができるかどうかなども設定できるとは。いやはや恐れ入りました。
確かに書き換えたり削除したりすることができない値もありますもんね。

```js
delete document.location  // false
```

参考: [delete演算子](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/delete)

オブジェクトのプロパティ定義方法についてを修めるつもりがプロパティの種類までわかってしまいました。棚ボタです！

ちなみにですが、この`descriptor`ですが、TypeScriptのDecoratorの文脈でも出てきます。([参考](https://www.typescriptlang.org/docs/handbook/decorators.html#decorator-composition))

# おわりに

> 検出の項目にはObject.prototype.propertyIsEnumerable()があるためとりあえず見てみましょう。

の部分にやや無理があることは承知していますが、持っていきたい方向があったため流れを優先しました。