Notice
Recent Posts
Recent Comments
관리 메뉴

즐겁게, 코드

객체 복사하기 (feat. 로대쉬) 본문

💬 언어/Javascript

객체 복사하기 (feat. 로대쉬)

Chamming2 2021. 2. 13. 02:23

자바스크립트의 자료형은 원시형과 객체 타입으로 구분됩니다.

원시형 : Number, String, Boolean
객체 : 나머지 전부

객체 타입이 원시형과 구분되는 가장 큰 차이는 변수에 실제 값이 담기는 원시형과는 달리 객체 타입은 해당 데이터의 참조(reference) 값이 저장된다는 것입니다. (C나 C++의 일반 타입과 포인터를 떠올리면 됩니다.)

 

이게 뭐가 중요하다고 갑자기 이 얘기를 꺼내는 걸까요?

이제부터 코드를 통해 살펴봅시다.

let number1 = 1;
let number2 = number1;

number2 = number1 + 9;
console.log(`number1 : ${number1}, number2 : ${number2}`);
// number1 : 1, number2 : 10

number2 변수에 number1 변수의 "값" 을 저장하고 여기에 9를 더해 출력하는 모습입니다.

누가 봐도 의심의 여지가 없는 결과지만, 객체의 경우에는 얘기가 조금 달라집니다.

let person1 = {
  name: "chanmin",
};

let person2 = person1;
person2.name = "Heejin";

console.log(`person1's name : ${person1.name}`);
console.log(`person2's name : ${person2.name}`);
// person1's name : Heejin
// person2's name : Heejin

person1 변수에는 {name: "chanmin"} 이라는 객체가 저장된 메모리 주소 가 저장되는데요, 따라서 person2에 person1을 대입하면 {name: "chanmin"} 값이 할당되는 것이 아니라 해당 값이 존재하는 메모리 주소가 할당되게 됩니다.

 

따라서, 결국 두 변수는 동일한 객체의 참조를 바라보게 되는 것이죠.

그럼 어떻게 해야 객체의 값만을 복사할 수 있을까요?

방법 1. for-in 문 사용하기

let person1 = {
  name: "chanmin",
};

let person2 = {};

for (let key in person1) {
  person2[key] = person1[key];
}

person2.name = "Heejin";

console.log(`person1's name : ${person1.name}`);
console.log(`person2's name : ${person2.name}`);

첫 번째 방법은 for-in 문을 사용하는 방법입니다.

for-in을 통해 객체의 프로퍼티를 순회할 수 있다는 점을 이용해, 객체의 값만을 복사하는 것이 가능합니다.

방법 2. Object.assign 사용하기

let person1 = {
  name: "chanmin",
};

let person2 = {};

Object.assign(person2, person1);
person2.name = "Heejin";

console.log(`person1's name : ${person1.name}`);
console.log(`person2's name : ${person2.name}`);

두 번째 방법은 Object 프로토타입의 assign 메서드를 활용하는 방법입니다.

Object.assign(dest, [src1, src2...]) 를 활용하면 src 객체의 프로퍼티와 값을 dest 객체에 그대로 복사할 수 있습니다.

 

그럼 이걸로 객체의 복사를 완전히 해결한 걸까요?

음... 만약 객체의 프로퍼티로 또다른 객체가 있다면요?

let person1 = {
  name: "chanmin",
  study: {
    languages: ["Javascript", "Python"],
  },
};

let person2 = {};

Object.assign(person2, person1);

person2.name = "Heejin";
person2.study.languages = ["C++", "Java"];

console.log(`person1 : ${person1.name} / ${person1.study.languages}`);
console.log(`person2 : ${person2.name} / ${person2.study.languages}`);
// person1 : chanmin / C++,Java
// person2 : Heejin / C++,Java

이런, 이대로라면 팔자에 없던 C++과 자바를 공부하게 생겼네요.

 

중첩된(nested) 객체를 복사할 때 문제가 생긴 이유는 for-in 이나 Object.assign을 활용하는 방법 모두 객체의 프로퍼티와 값을 복사하는데, 프로퍼티 값이 새로운 객체일 경우에는 해당 객체의 참조를 복사하게 되어 같은 객체를 가리키게 되는 문제가 재발하기 때문입니다.

 

따라서 중첩된 객체를 복사하기 위해서는  프로퍼티의 값이 객체인지를 검사한 후, 객체일 경우에는 재귀적으로 과정을 반복해주는 작업이 필요합니다.

재귀적으로 중첩 객체의 내용 복사하기

let person1 = {
  name: "chanmin",
  study: {
    languages: ["Javascript", "Python"],
  },
};

let person2 = {};

const recursiveCopy = (obj, obj2) => {
  for (key of Object.keys(obj)) {
    if (typeof obj[key] === "object") {
      obj2[key] = {};
      recursiveCopy(obj[key], obj2[key]);
    } else {
      obj2[key] = obj[key];
    }
  }
};

recursiveCopy(person1, person2);

person2.name = "Heejin";
person2.study.languages = ["C++", "Java"];

console.log(`person1 : ${person1.name} / ${person1.study.languages}`);
console.log(`person2 : ${person2.name} / ${person2.study.languages}`);
// person1 : chanmin / Javascript,Python
// person2 : Heejin / C++,Java

은근히 구현이 오래 걸렸네요 -ㅅ-;

 

그러나 위의 재귀 로직을 구상하는 것도 쉽지만은 않을 뿐더러, 이 코드가 컴포넌트에 추가되어야 한다면 비즈니스 로직에 집중하기 어려워집니다.

 

그래서! 배열과 객체를 보다 효율적으로 다루기 위한 로대시(loadash) 라는 라이브러리를 활용할 수 있는데요, 로대시에 내장된 cloneDeep 이라는 함수를 활용하면 보다 간편하게 중첩 객체의 복사를 수행할 수 있습니다.

clonedeep 함수를 사용해 중첩 객체의 내용 복사하기

npm i lodash.clonedeep // clonedeep 함수만을 불러옵니다.
const cloneDeep = require("lodash.clonedeep");

let person1 = {
  name: "chanmin",
  study: {
    languages: ["Javascript", "Python"],
  },
};

let person2 = {};

person2 = cloneDeep(person1);

person2.name = "Heejin";
person2.study.languages = ["C++", "Java"];

console.log(`person1 : ${person1.name} / ${person1.study.languages}`);
console.log(`person2 : ${person2.name} / ${person2.study.languages}`);
// person1 : chanmin / Javascript,Python
// person2 : Heejin / C++,Java

로대시를 사용하면 분명 편리하지만, 한번쯤은 재귀 복사 방법도 직접 손으로 구현해보는걸 강력히 추천드립니다.

분명 의미있는 삽질이 될거에요! 흐흐

TL;DR

  • 변수에 객체를 할당할 때는 객체의 참조를 할당한다.
  • 단일 객체를 깊은 복사(값만을 복사)하기 위해서는 for-in 문으로 키를 순회하거나 Object.assign(dest, [src1, src2...])을 활용할 수 있다.
  • 중첩된 객체를 깊은 복사하기 위해서는 재귀적으로 깊은 복사를 수행하거나 로대시의 clonedeep 함수를 활용할 수 있다.
반응형

'💬 언어 > Javascript' 카테고리의 다른 글

콜 스택과 이벤트 루프  (0) 2021.02.20
커링은 어려워  (0) 2021.02.18
렉시컬 스코프와 클로저  (0) 2021.02.10
이터레이터와 이터러블  (0) 2021.02.04
비밀을 지켜라! - 심볼형 프로퍼티 사용하기  (0) 2021.01.07
Comments
소소한 팁 : 광고를 눌러주시면, 제가 뮤지컬을 마음껏 보러다닐 수 있어요!
와!! 바로 눌러야겠네요! 😆