javascript オブジェクトの参照とコピーについて



## 挨拶
@hakeと申します。初投稿です！
オブジェクトの参照とコピーについて勉強し直したので記事にしました！
知っている方も復習する気持ちで読んでいただけると嬉しいです！

## 以下のコードには問題があります
```Index.tsx
  const [team, setTeam] = useState({
    engineer: { reader: "kazu", member: ["eita", "yasu"] },
    designer: { reader: "satoshi", member: ["keita", "tadashi"] },
  });

  // bad
  const setEngeneerReader = () => {
    // スプレッド構文でteamをコピーする
    const newTeam = { ...team };
    // 深くネストされた値を変更する
    newTeam.engineer.reader = "koki";
    console.log(
      "prevReader",
      team.engineer.reader,
      "newReader",
      newTeam.engineer.reader
    );
    // →出力：prevReader koki newReader koki

    // この時点でコピー元であるteam.engineer.readerが変更されている
    // →意図していない挙動

    setTeam(newTeam);
  };
```
Reactにはstateを直接変更しては行けないというルールがあるにもかかわらず、変更してしまっています。
https://ja.reactjs.org/docs/state-and-lifecycle.html#using-state-correctly

こういったミスをしないためにも参照やコピーについて理解が必要

## 参照とは
他の場所にあるデータを指している情報を含む小さなオブジェクトであり、それ自身の中に（指している）データ自体を含まない。

## 名前の例
名前を識別子とすると、実際の「人」が値で、値に情報がたくさん詰まっている。
→（例）「のび太」という名前はのび太くん本体（値）を参照していると言える。

のび太くん本体　← のび太
のび太くん本体　← のびくん
のび太くん本体　← のびちゃん
といったように複数の名前（あだ名）がのび太くん本体を参照している

## 参照によるメリット
複数のコードが参照によってひとつのデータを共有することができ、メモリの節約になる

## コピーの種類
 
### シャローコピー（浅いコピー）
オブジェクトの参照をコピーする
コピー元とコピー先で同じ値を参照する

### ディープコピー（深いコピー）
オブジェクトの値をコピー
コピー元とコピー先で別の値を参照する

## プリミティブ値のコピー
```Index.tsx
  let a = "a";
  let b = "b";
  a = b;
  b = "c";
  console.log(a);
  // →出力:"b"
  console.log(b);
  // →出力:"c"
```
プリミティブ値の場合は値がコピーされる

## オブジェクトのコピー
```Index.tsx
  let obj = {p1: "dora", p2: "nobi", p3: "shizu"}
  let obj2 = obj

  obj2.p1 = "sune"

  console.log(obj.p1)
  // →出力："sune"
  console.log(obj2.p1)
  // →出力:"sune"
```
オブジェクトの場合は値への参照がコピーされる
→obj2.p1を変更すると元の値が変更されてしまうので注意が必要！

とはいっても、コピー元を変更せずにコピー先を変更したいとき、ありますよね😩

## コピー元を変更せずにコピー先を変更する方法

### パターン１：Object.assign
```Index.tsx
  let obj = {p1: "dora", p2: "nobi", p3: "shizu"}
  let obj2 = Object.assign({}, obj)

  obj2.p1 = "sune"

  console.log(obj.p1);
  // →出力:"dora"
  console.log(obj2.p1);
  // →出力:"sune"
```

### パターン２：スプレッド演算子
```Index.tsx
  let obj = {p1: "dora", p2: "nobi", p3: "shizu"}
  let obj2 = {...obj}

  obj2.p1 = "sune"

  console.log(obj.p1);
  // →出力:"dora"
  console.log(obj2.p1);
  // →出力:"sune"
```

## 問題点
①②の問題点：プロパティにオブジェクトがネストされている場合、そのオブジェクトは参照がコピーされる。

```Index.tsx
  let obj = { p1: "nobi", p2: "shizu", robot: { r1: "doraemon", r2: "dorami" } };
  let obj2 = { ...obj };

  obj2.robot.r1 = "roomba";

  console.log(obj.robot.r1);
  // →出力:"roomba"
  // 同じ値を参照しているので元の値も変更されてしまう
  console.log(obj2.robot.r1);
  // →出力:"roomba"
```

つまり、上記のパターンはシャローコピー（浅いコピー）！！！
→深いネストのオブジェクトをコピーした場合はコビー元も変更されてしまう！！

##ディープコピーしたい場合

### パターン1:一度json文字列に変換する

```Index.tsx
  let obj = {p1: "nobita", p2: "shizu", robot: {r1: "doraemon", r2: "dorami"}}
  let obj2 = Object.assign({}, JSON.parse(JSON.stringify(obj)))

  obj2.robot.r1 = "roomba"

  console.log(obj.robot.r1);
  // →出力："doraemon"
  console.log(obj2.robot.r1);
  // →出力："roomba"
```
以下のオブジェクトはdeepCopyできないのであまり推奨されていない
・Dateオブジェクト
・function
・undefined

### パターン2:lodashのcloneDeep
```Index.tsx
  let obj = { p1: "nobi", p2: "shizu", robot: { r1: "doraemon", r2: "dorami" } };
  let obj2 = _.cloneDeep(obj);

  obj2.robot.r1 = "roomba";

  console.log(obj.robot.r1);
  // →出力："doraemon"
  console.log(obj2.robot.r1);
  // →出力："roomba"
```

## 問題のコードをもう一度見てみよう！

```Index.tsx
  const [team, setTeam] = useState({
    engineer: { reader: "kazu", member: ["eita", "yasu"] },
    designer: { reader: "satoshi", member: ["keita", "tadashi"] },
  });

  // bad
  const setEngeneerReader = () => {
    // teamをディープコピーする
    const newTeam = _.cloneDeep(team);
    // 深くネストされた値を変更する
    newTeam.engineer.reader = "koki";
    console.log(
      "prevReader",
      team.engineer.reader,
      "newReader",
      newTeam.engineer.reader
    );
    // →出力：prevReader koki newReader koki

    // この時点でコピー元であるteam.engineer.readerが変更されている
    // →意図していない挙動

    setTeam(newTeam);
  };
```

とは言っても、なるべくネストしないように定義するべきですね！！！

## おまけ
### 分割代入
```Index.tsx
  const obj = {
    p: "dora",
  };

  let { p } = obj;
  p = "nobi";

  console.log(obj.p, p);
  // →出力："dora" "nobi"

  const obj2 = {
    p1: {
      p2: "dora",
    },
  };

  let { p1 } = obj2;

  p1.p2 = "nobi";

  console.log(obj2.p1.p2, p1.p2);
  // →出力："nobi" "nobi"
```
分割代入の場合オブジェクトがネストされている場合、参照がコピーされるので注意！！


### 新しくインスタンス化
```Index.tsx
  class Team {
    public id: number = 0;
    public engineer: { reader: string; front: string[]; back: string[] } = {
      reader: "",
      front: [],
      back: [],
    };
    public designer: { reader: string; member: string[] } = {
      reader: "",
      member: [],
    };

    constructor(init?: Partial<Team>) {
      // assign
      Object.assign(this, init);
    }
  }

  const team1 = new Team({
    id: 1,
    engineer: {
      reader: "dora",
      front: ["atom", "haro"],
      back: ["r2", "c3"],
    },
    designer: {
      reader: "roomba",
      member: ["braaba", "eufy"],
    },
  });

  const team2 = new Team(team1);
  team2.engineer.reader = "ashimo";

  console.log(team1.engineer.reader, team2.engineer.reader);
  // →出力:"ashimo" "ashimo"
```
newでのインスタンス化はシャローコピーになる


▼複業でスキルを活かしてみませんか？複業クラウドの登録はこちら！
https://talent.aw-anotherworks.com/?login_type=none