【JavaScript 再入門】 オブジェクトの初期化と列挙編

## 前置き
ご無沙汰してます。
社内の勉強会やFlutterのキャッチアップでなかなかQiitaを書けずにいましたが、
落ち着いたので再開です！

前回の[JavaScript 再入門 基本構文編](https://qiita.com/naoya_matsuda/items/fe51c7105db9f32bb4df)に引き続き、JavaScript についてまとめます。
今回はオブジェクトについてです。

## オブジェクトとは

JavaScript のオブジェクトはキーバリュー形式(内部的には連想配列)で
表現されるプロパティの集合です。
JavaScript での以下の方法で初期化できます。

|方法|特徴|
|:---|:---|
| `new Object()` | プロパティを持たないオブジェクトオブジェクトの初期化 |
| `Object.create(proto, propertiesObject)` | Objectオブジェクトのcreateメソッドを使用し、プロパティを定義 |
| オブジェクトリテラル | リテラルを使用しプロパティの初期化を行う方法 |


### `new Object()`
JavaScriptにもともとある`Object`オブジェクトから明示的にオブジェクトを作成し、
その後、プロパティを定義していく方法です。

内部的にはオブジェクトリテラルを使用する方法と変わらず、
この方法で記載すると冗長になるため、
基本的にはオブジェクトリテラルを使用して初期化を行います。

```js
const user = new Object()
user.name = "matsuda-naoya" // ドット記法でのプロパティ定義方法
user["birthplace"] = "Ehime" // ブラケット記法でのプロパティ定義方法

console.log(user)

// 実行結果

// { 
//     name: 'matsuda-naoya', 
//     birthplace: 'Ehime' 
// }

```

### `Object.create(proto, propertiesObject)`
この方法で定義する際は他の方法と違い、
オブジェクトを初期化する際に継承元(prototype)を宣言すると同時に、
各プロパティに設定値を同時に定義できます。
以下の設定値を定義できます

|設定プロパティ|意味|初期値|詳細|
|:---|:---|:---:|:---|
|writable|書き込み可能有無|`false`|プロパティの上書きが行えるかの有無が設定できる項目<br>falseにするとプロパティ値に再代入しても無視され、`use strict`モードの場合エラーをスローする|
|enumerable|列挙可能有無|`false`|後述するプロパティを列挙する際に対象プロパティを列挙するか決める設定項目 |
|configurable|プロパティ設定の再定義有無|`false`|`Object.defineProperty()`等を使用しプロパティの設定を変更することが行えるかの設定項目<br>※configurableがtrueで、writableがfalseの場合、プロパティの値を書き換えることが可能なため注意が必要|

```js
const user = Object.create(Object.prototype, {
    name: { // 書き換え不可で列挙対象プロパティ
        value: "matsuda-naoya",
        writable: false, 
        enumerable: true
    },
    birthplace: { // 書き換え不可で列挙対象プロパティ
        value: "Ehime",
        writable: false, 
        enumerable: true
    },
    favoriteLanguage: { // 列挙対象プロパティ
        value: "JavaScript",
        enumerable: true,
        writable: true, 
    },
    birthday: { // 書き換え不可で列挙非対象プロパティ
        value: "匕・ミ・ツ",
        enumerable: false 
    }
})

user.name = "名前の変更は簡単にはできない"
user.favoriteLanguage = "TypeScript"

console.log(user)
// 実行結果

// { name: 'matsuda-naoya',
//   birthplace: 'Ehime',
//   favoriteLanguage: 'TypeScript' }

```

### オブジェクトリテラル
JavaScriptを使用する上で最も頻繁に出現するオブジェクト初期化方法かと思います。

```js
const style = "イケメン"
const favoriteLanguageConst = "favorite_language"

const homeTown = "homeTown"
const object = {
    "name": "matsuda-naoya", // nameキーに値matsudaを定義
    name: "matsuda-naoya", // プロパティ名(キー)は「" '」を省略化
    first-name: "naoya", // エラー jsの変数名に使えない値は
    "1th-name": "naoya", // " '」を省略できない
    style, // 【ES2016】プロパティ名と値の変数名が同じ値の場合省略化
    [favoriteLanguageConst]: "JavaScript"　// 【ES2016】式の評価値をプロパティ名にできる
}
```

```js
// プロパティへのアクセス
object.name; // ドット記法「.」でのアクセス
object.first - name; // error 識別子の命名規則から逸脱しているプロパティへのドット記法でのアクセスは不可
object["first-name"]; // ブラケット記法「[]」でのアクセス
```

## プロパティの列挙

下記は JavaScript でオブジェクトのプロパティ一覧を列挙する方法です。

|EcmaScriptバージョン|方法|UnEnumrableな値の表示|prototypeの列挙有無|列挙対象値|
|:---|:---|:---:|:---:|:---|
|ES1| `for...in` | 無 | 有 | プロパティ名 |
|ES5.1| `Object.keys()` | 無 | 無 | プロパティ名 |
|ES5.1| `Object.getOwnPropertyNames()` | **有** | 無 | プロパティ名 |
|ES2015| `Object.values()` | 無 | 無 | プロパティ値 |
|ES2017| `Object.entries()` | 無 | 無 | プロパティ名と値 |

### for...in
`for (変数宣言 変数名 in 列挙対象オブジェクト)`
とすることでオブジェクトの列挙可能なプロパティを列挙します

```js

const user = {
    name: "matsuda-naoya",
    birthplace: "Ehime",
    favoriteLanguage: "JavaScript",
    birthday: "匕・ミ・ツ"
}

for (let key in user) {
    console.log(`key: ${key}`) // 1. for...in文で宣言した変数からプロパティ名を表示
    console.log(`value: ${user[key]}`) // 2. 対象オブジェクトからfor...in文で宣言した変数を使用してプロパティ値を表示
    console.log(`value: ${user.key}`) // 3. 対象オブジェクトのプロパティにドット演算子でアクセスすると`undefined`になる
}
// 実行結果

// key: name
// value: matsuda-naoya
// value: undefined
// key: birthplace
// value: Ehime
// value: undefined
// key: favoriteLanguage
// value: JavaScript
// value: undefined
// key: birthday
// value: 匕・ミ・ツ
// value: undefined

```

### Object.keys()
`Object.keys(列挙対象オブジェクト)`とすることで列挙可能なプロパティ名一覧が**配列**としてレスポンスされる

```js
const user = {
    name: "matsuda-naoya",
    birthplace: "Ehime",
    favoriteLanguage: "JavaScript",
    birthday: "匕・ミ・ツ"
}

const userProperties = Object.keys(user)
console.log(userProperties) // [ 'name', 'birthplace', 'favoriteLanguage', 'birthday' ]
userProperties.forEach(function(key) {
    console.log(`key: ${key}`) 
    console.log(`value: ${user[key]}`)
})

// forEach内の実行結果

// key: name
// value: matsuda-naoya
// key: birthplace
// value: Ehime
// key: favoriteLanguage
// value: JavaScript
// key: birthday
// value: 匕・ミ・ツ
```

### Object.getOwnPropertyNames()
`Object.getOwnPropertyNames(列挙対象オブジェクト)`とすることで
対象オブジェクトのプロトタイプを除く全てのプロパティが**配列**としてレスポンスされる


```js

const user = Object.create(Object.prototype, {
    name: {
        value: "matsuda-naoya",
        enumerable: true
    },
    birthplace: {
        value: "Ehime",
        enumerable: true
    },
    favoriteLanguage: {
        value: "JavaScript",
        enumerable: true
    },
    birthday: {
        value: "匕・ミ・ツ",
        enumerable: false // enumerable(列挙可能)をfalse(不可)にすることでfor...in, Object.keysでは列挙されなくなる
    }
})

const userProperties = Object.keys(user) 
console.log(userProperties) // [ 'name', 'birthplace', 'favoriteLanguage' ]
userProperties.forEach(function(key) {
    console.log(`key: ${key}`) 
    console.log(`value: ${user[key]}`)
})

// 実行結果

// key: name
// value: matsuda-naoya
// key: birthplace
// value: Ehime
// key: favoriteLanguage
// value: JavaScript

// ※birthdayプロパティは表示されない

const userProperties = Object.getOwnPropertyNames(user)
console.log(userProperties) // [ 'name', 'birthplace', 'favoriteLanguage', 'birthday' ]
userProperties.forEach(function(key) {
    console.log(`key: ${key}`) 
    console.log(`value: ${user[key]}`)
})

// 実行結果

// key: name
// value: matsuda-naoya
// key: birthplace
// value: Ehime
// key: favoriteLanguage
// value: JavaScript
// key: birthday
// value: 匕・ミ・ツ

// ※birthdayプロパティも表示される
```

### Object.values()
Object.valuesは列挙対象のプロパティのうち列挙可能なプロパティの価を配列として返却する

```js
const userPropValues = Object.values(user)
console.log(userPropValues)
userPropValues.forEach(function(value) {
    console.log(`value: ${value}`)
})
// 実行結果

// value: matsuda-naoya
// value: Ehime
// value: JavaScript
// value: 匕・ミ・ツ
```

### 【ES2015】Object.entries
`Object.entries(列挙対象オブジェクト)`とすることで対象オブジェクトの列挙可能プロパティ名と**プロパティ値**を**2次元配列**として返却する

```js
const user = {
    name: "matsuda-naoya",
    birthplace: "Ehime",
    favoriteLanguage: "JavaScript",
    birthday: "匕・ミ・ツ"
}

const userProperties = Object.entries(user)
console.log(userProperties) 
// [ 
//     [ 'name', 'matsuda-naoya' ],[ 'birthplace', 'Ehime' ],[ 'favoriteLanguage', 'JavaScript' ],[ 'birthday', '匕・ミ・ツ' ] 
// ]
userProperties.forEach(function(innerArray) { // プロパティごとに列挙
    innerArray.forEach(function(value, index) { // プロパティの中身key, valueを列挙
        if (index === 0) console.log(`key: ${value}`)
        if (index === 1) console.log(`value: ${value}`)
    })
})
// 実行結果

// key: name
// value: matsuda-naoya
// key: birthplace
// value: Ehime
// key: favoriteLanguage
// value: JavaScript
// key: birthday
// value: 匕・ミ・ツ
```

#### 余談
Object.keysは分割代入とiteratableなオブジェクト(配列: Arrayや文字オブジェクト: String)で
使用できる、`for...of`を使用することで、シンプルに書けます

```js
    const userProperties = Object.entries(user)
    for ([key, value] of userProperties) {
        console.log(`key: ${key}`)
        console.log(`value: ${value}`)
    }
```
Arrayオブジェクトやその周辺に関する構文についてはまた別でまとめようと思います。

### まとめ
今回はオブジェクトの定義方法と列挙方法についてまとめました。

が、今回も、prototypeの説明まで行きませんでした。。。
インプットしながらアウトプットし続けるのって大変ですね。。。

確実に理解を深めながら学べているので、今後も続けていく予定です。