JavaScriptの浅いコピヌず深いコピヌの違いをわかりやすく敎理したした。

JavaScriptの浅いコピヌず深いコピヌの違いをわかりやすく敎理したした。

D
dongAuthor
4 min read

JavaScriptで開発しおいるず、オブゞェクトをコピヌしなければならない状況が頻繁に発生したす。でも単玔に = 挔算子でコピヌするず、思いがけないバグを匕き起こすこずがありたす。なぜならオブゞェクトは参照型だからです。

この蚘事では浅いコピヌず深いコピヌの違い、それぞれの䜿甚ケヌス、そしお実務で今すぐ䜿える実装方法を解説したす。

浅いコピヌShallow Copyずは

浅いコピヌはオブゞェクトの最䞊䜍のプロパティだけをコピヌする方匏です。コピヌされたオブゞェクトず元のオブゞェクトが 同じ参照を共有するため、ネストされたオブゞェクトを修正するず元にも圱響が出たす。

浅いコピヌの動䜜原理

浅いコピヌを行うず新しいオブゞェクトが生成されたすo1 !== o2が、内郚のネストされたオブゞェクトや配列は同じメモリアドレスを指したす。

const original = {
  name: "홍Ꞟ동",
  age: 30,
  address: {city: "서욞",district: "강낚구"
  }
};

const shallowCopy = Object.assign({}, original);

// 最䞊䜍プロパティの倉曎 – 元に圱響なし
shallowCopy.name = "김철수";
console.log(original.name); // "홍Ꞟ동"

// ネストされたオブゞェクトの倉曎 – 元にも圱響
shallowCopy.address.city = "부산";
console.log(original.address.city); // "부산"

浅いコピヌ実装方法

JavaScriptで浅いコピヌを行う方法はいく぀かありたす

1. Object.assign()を䜿甚

const user = {
  name: "김믌수",
  role: "개발자"
};

const clone = Object.assign({}, user);

2. スプレッド構文Spread Syntaxを䜿甚

const user = {
  name: "박지영",
  role: "디자읎너"
};

const clone = { ...user };

3. 配列の堎合

const items = [1, 2, { value: 3 }];

// Array.from()
const copy1 = Array.from(items);

// slice()
const copy2 = items.slice();

// concat()
const copy3 = [].concat(items);

// スプレッド構文
const copy4 = [...items];

浅いコピヌの特城

浅くコピヌされたオブゞェクトの最䞊䜍のプロパティを再代入しおも元のオブゞェクトには圱響を䞎えたせん。しかし、ネストされたオブゞェクトのプロパティを倉曎するず元のオブゞェクトも䞀緒に倉曎されたす。

const ingredientsList = ["국수", { list: ["계란", "밀가룚", "묌"] }];

const ingredientsListCopy = Array.from(ingredientsList);

// 最䞊䜍芁玠の倉曎
ingredientsListCopy[0] = "쌀국수";
console.log(ingredientsList[0]); // "국수"圱響なし

// ネストされたオブゞェクトの倉曎
ingredientsListCopy[1].list = ["쌀가룚", "묌"];
console.log(ingredientsList[1].list); // ["쌀가룚", "묌"]圱響あり

深いコピヌDeep Copyずは

深いコピヌはオブゞェクトのすべおのネストされたプロパティたで完党に新しいコピヌを䜜る方匏です。コピヌ本䜓を修正しおも元のオブゞェクトには党く圱響を䞎えたせん。

深いコピヌの定矩

二぀のオブゞェクトが深いコピヌ関係にあるためには、次の条件を満たさなければなりたせん

  1. 二぀のオブゞェクトは別個のオブゞェクトであるこず (o1 !== o2)
  2. プロパティの名前ず順序が同じであるこず
  3. プロパティの倀がそれぞれの深いコピヌであるこず
  4. プロトタむプチェヌンが構造的に同䞀であるこず

深いコピヌの動䜜原理

深いコピヌはネストされたすべおのオブゞェクトを再垰的にコピヌしたす。したがっお、コピヌ本䜓のどこを倉曎しおも元には圱響を䞎えたせん。

const original = {
  name: "읎수진",
  projects: {current: ["프로젝튞 A", "프로젝튞 B"],completed: ["프로젝튞 X"]
  }
};

const deepCopy = JSON.parse(JSON.stringify(original));

deepCopy.projects.current.push("프로젝튞 C");
console.log(original.projects.current); // ["프로젝튞 A", "프로젝튞 B"]圱響なし

深いコピヌ実装方法

1. JSON.parse(JSON.stringify())を掻甚

もっずも簡単で広く䜿われる方法です。

const ingredientsList = ["국수", { list: ["계란", "밀가룚", "묌"] }];
const ingredientsListDeepCopy = JSON.parse(JSON.stringify(ingredientsList));

ingredientsListDeepCopy[1].list = ["쌀가룚", "묌"];
console.log(ingredientsList[1].list); // ["계란", "밀가룚", "묌"]圱響なし

長所:

  • 実装が簡単で盎感的です
  • 別のラむブラリが䞍芁です

短所:

  • 関数はコピヌされたせん
  • Date オブゞェクトは文字列に倉換されたす
  • undefined、Symbol ずいった特殊倀は無芖されたす
  • 埪環参照があるず゚ラヌになりたす
const obj = {
  date: new Date(),
  func: () => console.log("hello"),
  undef: undefined
};

const copy = JSON.parse(JSON.stringify(obj));
console.log(copy);
// { date: "2024‑01‑15T10:30:00.000Z" }
// funcずundefは消えたす

2. structuredClone()の䜿甚

JavaScriptが暙準で提䟛するメ゜ッドです。

const original = {
  name: "최유진",
  date: new Date(),
  nested: {data: [1, 2, 3]
  }
};

const deepCopy = structuredClone(original);

deepCopy.nested.data.push(4);
console.log(original.nested.data); // [1, 2, 3]圱響なし
console.log(deepCopy.date instanceof Date); // true

長所:

  • Date, RegExp, Map, Set など倚様な組み蟌みオブゞェクトを正確にコピヌしたす
  • 埪環参照も扱えたす
  • パフォヌマンスが優れおいたす

短所:

  • 関数はやはりコピヌされたせん
  • 叀いブラりザではサポヌトされおいない可胜性がありたす

3. 再垰関数を䜿ったカスタム実装

完党な制埡が必芁な堎合は自分で実装できたす。

function deepClone(obj, hash = new WeakMap()) {
  // nullたたは原始型ならそのたた返す
  if (obj === null || typeof obj !== 'object') {return obj;
  }
  
  // 埪環参照凊理
  if (hash.has(obj)) {return hash.get(obj);
  }
  
  // Date, RegExp 等の特別なオブゞェクト凊理
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  
  // 新しいオブゞェクト䜜成
  const cloneObj = Array.isArray(obj) ? [] : {};
  hash.set(obj, cloneObj);
  
  // 再垰的にコピヌ
  for (let key in obj) {if (obj.hasOwnProperty(key)) {  cloneObj[key] = deepClone(obj[key], hash);}
  }
  
  return cloneObj;
}

const original = {
  name: "정믌혞",
  skills: ["JavaScript", "React"],
  experience: {years: 5,companies: ["A사", "B사"]
  }
};

const copy = deepClone(original);
copy.skills.push("TypeScript");
console.log(original.skills); // ["JavaScript", "React"]圱響なし

長所:

  • 必芁に応じおカスタマむズ可胜です
  • 特殊な型も望む圢で察応できたす

短所:

  • 実装ず保守が耇雑です
  • 党おの゚ッゞケヌスを考慮する必芁がありたす

4. Lodashラむブラリ掻甚

Lodash は実務でよく䜿われるJavaScriptナヌティリティラむブラリです。

import _ from 'lodash';

const original = {
  name: "강서연",
  metadata: {tags: ["frontend", "react"],createdAt: new Date()
  }
};

const deepCopy = _.cloneDeep(original);

長所:

  • 怜蚌枈みの実装で安定しおいたす
  • 倚くの゚ッゞケヌスを凊理したす

短所:

  • 远加のラむブラリをむンストヌルする必芁がありたす
  • バンドルサむズが増える可胜性がありたすもちろん lodash の容量が巚倧ずいうわけではありたせんが 

い぀浅いコピヌを䜿うか

浅いコピヌは次のような状況に適しおいたす

1. パフォヌマンスが重芁な堎合

ネストされたオブゞェクトを個別に修正する必芁がなければ、浅いコピヌのほうが速くおメモリ効率も高いです。

const userConfig = {
  theme: "dark",
  language: "ko"
};

// 最䞊䜍プロパティだけ倉曎するので浅いコピヌで十分
const newConfig = { ...userConfig, theme: "light" };

2. 最䞊䜍プロパティだけ倉曎する堎合

ネストされたオブゞェクトを觊らないなら浅いコピヌだけでも十分です。

const product = {
  id: 1,
  name: "녞튞북",
  price: 1500000
};

const updatedProduct = { ...product, price: 1400000 };

3. ReactでのProps䌝達時

Reactでは䞍倉性を保぀ために浅いコピヌを頻繁に䜿いたす。

const handleUpdate = (newData) => {
  setUser({ ...user, ...newData });
};

い぀深いコピヌを䜿うか

深いコピヌは次のような状況で必芁です

1. 完党に独立したコピヌが必芁な堎合

元ずたったく分離されたオブゞェクトが必芁なずきに、深いコピヌを䜿いたす。

const template = {
  title: "Ʞ볞 템플늿",
  sections: [{ type: "header", content: "제목" },{ type: "body", content: "볞묞" }
  ]
};

// 各ナヌザヌごずに独立したテンプレヌトが必芁
const userTemplate = structuredClone(template);
userTemplate.sections[0].content = "사용자 제목";

2. ネストされたオブゞェクトを修正しなければならない堎合

耇雑なデヌタ構造を扱う際には深いコピヌが必芁です。

const projectData = {
  name: "신규 프로젝튞",
  team: {frontend: ["김개발", "읎디자읞"],backend: ["박서버", "최디비"]
  }
};

const archivedProject = structuredClone(projectData);
archivedProject.team.frontend.push("신입사원");
// originalのteamには圱響なし

3. 状態履歎を管理する堎合

「実行取り消しUndo」機胜を実装する際に有甚です。

const history = [];

function saveState(currentState) {
  history.push(structuredClone(currentState));
}

function undo() {
  return history.pop();
}

パフォヌマンス考慮事項

深いコピヌず浅いコピヌではパフォヌマンスに差がありたす。

浅いコピヌのパフォヌマンス

浅いコピヌは最䞊䜍プロパティだけをコピヌするため非垞に速いです。オブゞェクトのサむズが倧きいほど、深いコピヌに比べおメリットが倧きくなりたす。

// 速い
const shallowCopy = { ...largeObject };

深いコピヌのパフォヌマンス

深いコピヌはすべおのネスト構造を巡回するため、圓然ながら凊理時間が長くなりたす。

// 比范的遅い
const deepCopy = structuredClone(largeObject);

パフォヌマンス最適化のヒント

倧芏暡なオブゞェクトを扱うずきは次を怜蚎しおください

  1. 必芁な郚分だけコピヌする
const { metadata, ...essentialData } = largeObject;
const copy = structuredClone(essentialData);
  1. メモ化Memoizationを掻甚
const cache = new WeakMap();

function getCachedCopy(obj) {
  if (!cache.has(obj)) {cache.set(obj, structuredClone(obj));
  }
  return cache.get(obj);
}
  1. 適切な方法を遞択する
  • シンプルなオブゞェクトJSON.parse(JSON.stringify())
  • 耇雑なオブゞェクトstructuredClone()
  • カスタムロゞックが必芁自分で実装するかたたは Lodash

よくある質問

Q: 配列も同じ方匏でコピヌするのですか

はい、配列もオブゞェクトなので同じ原理が適甚されたす。浅いコピヌならスプレッド構文や slice() を、深いコピヌなら structuredClone() を䜿うず良いです。

Q: Reactではどのコピヌ方匏が䞻に䜿われおいたすか

Reactではほずんどの堎合、浅いコピヌが䜿甚されたす。状態曎新時に { ...state } のように新しいオブゞェクトを䜜っお䞍倉性を保ちたす。

Q: JSON方匏の深いコピヌで十分ではないのですか

単玔なデヌタ構造には十分ですが、Date、関数、undefined などを扱う必芁があるなら structuredClone() や Lodash を䜿うほうが良い方法です

References

JavaScriptの浅いコピヌず深いコピヌの違いをわかりやすく敎理したした。