【JavaScript】オブジェクト② 〜

# 概要
...[【JavaScript】オブジェクト①](https://qiita.com/andota05/items/86f359455eb4a233031a)の続きです。

JavaScriptを学習、理解を深めるため「[JavaScript Primer 迷わないための入門書](https://jsprimer.net/)」を読み、
理解した内容等を記載していく。

「[【JavaScript】JavaScript入門一覧](https://qiita.com/andota05/items/22fae452225b785fd405)」に他の記事をまとめています。

# この記事で理解できること
* オブジェクトのプロパティの追加方法

# プロパティの追加
* オブジェクトは、プロパティの追加など`オブジェクト作成後も値を変更できる。`
* オブジェクトに指定したプロパティが存在しない場合、`自動的にプロパティが作成される。`
* `ドット記法`、`ブラケット記法`のどちらでも追加可能。
* 追加時にプロパティ名(キー)を`変数`、`変数の命名規則には適合しない文字列`、`Symbol`を指定する際は、ブラケット記法を使用する。

```js
// 空のオブジェクト「obj」を作成
const obj = {};

// nameプロパティをドット記法で追加して値を代入
obj.name = "Taro";             // 自動的にnameプロパティが作成される

// 変数「key」に変数の命名規則には適合しないプロパティ名「e-mail」を代入
const key = "e-mail";
obj[key] = "sample@mail.com";  // 変数「key」に代入したe-mailプロパティが作成、および指定した値が設定される

// Symbolのプロパティを追加
obj[Symbol("hobby")] = "baseball";

console.log(obj); // => {name: 'Taro', e-mail: 'sample@mail.com', Symbol(hobby): 'baseball'}
```

# 【Tips】Computed property names
* ブラケット記法を用いたプロパティの定義は、オブジェクトリテラル`{ }`内でも可能。
* その際、プロパティ名は`「Computed property names」`と呼ばれる。
* 「Computed property names」は`ES2015から導入された記法`

```object.js
// 変数「key」に変数の命名規則には適合しないプロパティ名「e-mail」を代入
const key = "e-mail";

// 空のオブジェクト「obj」を作成
const obj = {
    [key]: "sample@mail.com"  //　　=>　　変数「key」が評価されプロパティ名「e-mail」の設定、および値が設定される
};

// 変数「key」をブラケット記法で指定
console.log(obj[key]);        // => sample@mail.com
```

## 注意点
* 冒頭で記載した通り、JavaScriptのオブジェクトは`オブジェクト作成後にプロパティを変更可能`という特性を持つ。
* オブジェクトの作成時以外でプロパティを追加すると、`オブジェクトがどんなプロパティを持っているか不明確`になりやすい。
* できる限りオブジェクト作成後に新しいプロパティは追加せず、`オブジェクトの作成時のオブジェクトリテラルの中でプロパティを定義する`ことが推奨されている。

# プロパティの削除
* オブジェクトのプロパティを削除する場合は、`delete演算子`を使用する。
* ドット記法の場合`delete オブジェクト.プロパティ名;`で削除可能。
* ブラケット記法の場合`delete オブジェクト[プロパティ名];`で削除可能。

```object.js
// 3つのプロパティを持つ、オブジェクト「obj」を作成
const obj = {
    "name": "Taro",
    "age": 24,
    "e_mail": "main@mail.com",
};

// オブジェクト「obj」からプロパティ「age」を削除
delete obj.age;

// オブジェクト「obj」からプロパティ「e_mail」を削除
delete obj["e_mail"];

console.log(obj);  // => {name: 'Taro'}
```

# プロパティの存在を確認する
JavaScriptでは、存在しないプロパティにアクセスした場合、`例外`ではなく`undefined`を返す。
あるオブジェクトがあるプロパティを持っているかを確認する方法として、次の3つがある。

* undefinedとの比較
* in演算子
* hasOwnPropertyメソッド

## undefinedとの比較
* 存在しないプロパティにアクセスした場合`undefined`が返されるため、プロパティにアクセスすると存在を確認できる。
* しかし、プロパティの値が`undefined`だった場合、区別が難しくなる。
* そのため、プロパティが存在するかの判定には`in演算子`か`hasOwnPropertyメソッド`を使用する。

```object.js
// オブジェクト「obj」を作成
const obj = {
    "name": "Taro",
    "age": undefined
};

// 事前に確認してみる
console.log(obj.name);  // => Taro
console.log(obj.age);   // => undefined(値がundefined)
console.log(obj.not);   // => undefined(存在しないプロパティを指定した場合の挙動)

// オブジェクト「obj」にプロパティ「name」が存在するか確認
if (obj.name !== undefined) {
    // undefinedじゃなければconsole.logが実行される
    console.log("undefinedではない");  // => undefinedではない
}

// オブジェクト「obj」にプロパティ「age」が存在するか確認
if (obj.age !== undefined) {
    // undefinedじゃなければconsole.logが実行される
    console.log("undefinedではない");
}
// => プロパティageが存在しているが何も表示されない = プロパティの値がundefinedのためif文内の処理が実行されていない
```

## in演算子
* `"プロパティ名" in オブジェクト;`で指定したオブジェクトにプロパティが存在するかを判定できる。
* in演算子は`プロパティの値は関係なく、プロパティが存在するか`の判定を行う。
* `ture`もしくは`false`が返される。

```object.js
// オブジェクト「obj」を作成
const obj = {
    "name": "Taro",
    "age": undefined
};

// "プロパティ名" in オブジェクト;でプロパティが存在するかを判定
console.log("name" in obj);  // => true
console.log("age" in obj);   // => 値はundefinedだがプロパティが存在するためtrue
console.log("city" in obj);  // => プロパティが存在しないためfalse
```

## hasOwnPropertyメソッド
* `オブジェクト.hasOwnProperty("プロパティ名");`でプロパティが存在するかを判定できる。
* hasOwnPropertyメソッドの引数には、`存在を判定したいプロパティ名を指定する。`
* hasOwnPropertyメソッドも`プロパティの値は関係なく、プロパティが存在するか`の判定を行う。
* `ture`もしくは`false`が返される。

```object.js
// オブジェクト「obj」を作成
const obj = {
    "name": "Taro",
    "age": undefined
};

// オブジェクト.hasOwnProperty("プロパティ名");でプロパティが存在するかを判定
console.log(obj.hasOwnProperty("name"));  // => true
console.log(obj.hasOwnProperty("age"));   // => 値はundefinedだがプロパティが存在するためtrue
console.log(obj.hasOwnProperty("city"));  // => プロパティが存在しないためfalse
```

# 【Tips】Optional chaining演算子（?.）
* ES2020では`Optional chaining演算子（?.）`が導入された。
* `ネストしたプロパティの存在確認`と`アクセス`を簡単に行えるのが特徴。
* (プロパティを持つオブジェクトの場合)`オブジェクト?.プロパティ名`と指定しアクセスする。
* 左辺のアクセス対象が`null`または`undefined`の場合は、それ以上評価せず`undefined`を返す。
* プロパティが存在する場合は、`プロパティの評価結果`を返す。

```object.js
// オブジェクト「obj」を作成
const obj = {
    "team_a": {
        "name": "Taro",
        "age": undefined,
        "e_mail": "main@mail.com"
    }
};

// ドット記法やブラケット記法の場合、ネストしたプロパティのいずれかが「undefined」の場合、例外が発生してしまう
console.log(obj.team_b.name);        // => Uncaught TypeError: Cannot read properties of undefined (reading 'name')
console.log(obj["team_b"]["name"]);  // => Uncaught TypeError: Cannot read properties of undefined (reading 'name')

// オブジェクト.hasOwnProperty("プロパティ名");でプロパティが存在するかを判定
console.log(obj?.team_a?.name);        // => Taro
console.log(obj?.team_a?.age);         // => undefined
console.log(obj?.team_a?.["e_mail"]);  // => main @mail.com(ブラケット記法との組み合わせも可能)
console.log(obj?.team_b?.name);        // => 例外の発生、「team_b」以降の評価はされず「team_b」の評価結果「undefined」が返される
```

# toStringメソッド
* toStringメソッドは`オブジェクト自身を文字列化するメソッド。`
* `Stringコンストラクタ関数`でこのtoStringメソッドが呼び出されている。
* `オブジェクト.toString();`でオブジェクトを文字列化可能。

```object.js
// オブジェクト「obj」を作成
const obj = {
    "team_a": {
        "name": "Taro",
        "age": undefined,
        "e_mail": "main@mail.com"
    }
};

// オブジェクト「obj」をtoStringメソッドで文字列化する
console.log(obj.toString());  // => オブジェクトを文字列化すると[object Object]という文字列になる
```

# オブジェクトの静的メソッド(スタティックメソッド)
* オブジェクトの静的メソッドとは、`インスタンスの元となる「Object」そのものから呼び出せるメソッド`のことである
* `オブジェクトの列挙やマージ`などオブジェクトを操作する静的メソッドが存在する。

## オブジェクトの列挙
オブジェクトのプロパティを列挙する３つの以下の静的メソッドが存在する。

* Object.keysメソッド(オブジェクトのプロパティ名を配列で返す)
* Object.valuesメソッド(オブジェクトの値を配列で返す) ※ES2017から
* Object.entriesメソッド(オブジェクトのプロパティ名と値の配列を返す) ※ES2017から

```object.js
// オブジェクト「obj」を作成
const obj = {
    "name": "Taro",
    "age": undefined,
    "e_mail": "main@mail.com"
};

// Object.keys(オブジェクト);でプロパティ名を配列で返す
console.log(Object.keys(obj));    // => (3) ['name', 'age', 'e_mail']

// Object.values(オブジェクト);でオブジェクトの値を配列で返す
console.log(Object.values(obj));  // => (3) ['Taro', undefined, 'main@mail.com']

// Object.entries(オブジェクト);でオブジェクトのプロパティ名と値の配列を返す
console.log(Object.entries(obj));  // => [Array(2), Array(2), Array(2)]
                                   //     0: (2)['name', 'Taro']
                                   //     1: (2) ['age', undefined]
                                   //     2: (2) ['e_mail', 'main@mail.com'] ...
```

## オブジェクトのマージと複製
* `Object.assgin`メソッドは、`あるオブジェクトを別のオブジェクトに代入する`ことが可能。
* オブジェクトの`複製やマージ`を行うことが可能。
* 既存のオブジェクトには影響を与えずマージしたオブジェクトを作ることができるため、`第一引数(上記ではコピー先オブジェクト)には空のオブジェクトを指定すること`が典型的な方法とされている。
* プロパティ名が重複した場合は、`後ろのオブジェクトのプロパティにより上書きされる`ことに注意。
* ES2015から使用可能。

```object.js
// オブジェクト「returnObj」を作成
const returnObj = {};

const objA = {
    "name": "Hanako",
    "age": 22,
    "e_mail": "main@mail.com"
};

const objB = {
    "name": "Eita",
    "age": 28
};

// 結果的にobjAを複製した形でオブジェクト「returnObj」が返される
console.log(Object.assign(returnObj, objA));        // => {name: 'Hanako', age: 22, e_mail: 'main@mail.com'}

// objAとobjBではプロパティ「name」と「age」が重複しているためobjAのプロパティの値が上書きされている
console.log(Object.assign(returnObj, objA, objB));  // => {name: 'Eita', age: 28, e_mail: 'main@mail.com'}
```

# 【Tips】オブジェクトのspread構文(...)
* ES2018から`オブジェクトのspread構文(...)`が追加された。
* ES2015で`配列の要素を展開するspread構文`はサポートされていたが、オブジェクトに対しては`ES2018`からサポートされた。
* 新たにオブジェクトを作成する`オブジェクトリテラル({ })`の中に指定し、使用する。
* プロパティ名が重複した場合は、`後ろのオブジェクトのプロパティにより上書きされる`ことに注意。

```object.js
const objA = {
    "name": "Hanako",
    "age": 22,
    "e_mail": "main@mail.com"
};

const objB = {
    "name": "Eita",
    "age": 28
};

// spread構文でマージするオブジェクトを指定
const returnObj = {
    ...objA,
    ...objB
};

// マージしたオブジェクトを確認してみる
console.log(returnObj);  // => {name: 'Eita', age: 28, e_mail: 'main@mail.com'}
```