본문 바로가기

프로그래밍언어/JavaScript

[JavaScript] 객체 복사와 Object.assign의 활용

객체의 변경과 사이드 이펙트


배열과 마찬가지로 객체도 필드를 추가하거나 설정할 때 **사이드 이펙트(Side Effect)**가 발생할 수 있다. 특히, 기존 데이터를 유지하면서 새로운 필드를 추가하거나 기본값으로 채우려면 신중한 처리가 필요하다.

예를 들어, 아래와 같은 기본값 객체와 새로 추가할 데이터를 가진 객체를 보자.

const defaults = {
  author: '',
  title: '',
  year: 2017,
  rating: null
};

const book = {
  author: 'Joe Morgan',
  title: 'Simplifying JavaScript'
};

 

 

기존 방식: Object.keys와 반복문 활용


defaults와 book을 병합하려면 아래와 같은 방식으로 각 필드를 처리할 수 있다.

function addBookDefault(book, defaults) {
  const fields = Object.keys(defaults);
  const updated = {};
  for (let i = 0; i < fields.length; i++) {
    const field = fields[i];
    updated[field] = book[field] || defaults[field];
  }
  return updated;
}

const updatedBook = addBookDefault(book, defaults);
console.log(updatedBook);
// { author: 'Joe Morgan', title: 'Simplifying JavaScript', year: 2017, rating: null }

 

위 코드는 정확히 작동하지만, 코드가 다소 복잡하고 반복문을 사용해 명시적으로 처리해야 한다는 단점이 있다.

 

 

Object.assign을 사용한 간단한 병합


Object.assign 메서드를 사용하면 객체 병합 작업을 훨씬 간단하게 처리할 수 있다.

const updatedBook = Object.assign({}, defaults, book);
console.log(updatedBook);
// { author: 'Joe Morgan', title: 'Simplifying JavaScript', year: 2017, rating: null }

Object.assign은 첫 번째 매개변수로 대상 객체를 받고, 나머지 매개변수로 주어진 객체의 모든 속성을 대상 객체에 복사한다. 이 방법은 코드의 간결성을 크게 높여준다.

 

 

원본 객체 변경 문제


 다만, Object.assign은 원본 객체를 변경할 수 있다는 문제가 있다. 예를 들어, 아래 코드는 defaults 객체 자체를 변경한다.

Object.assign(defaults, book);
console.log(defaults);
// { author: 'Joe Morgan', title: 'Simplifying JavaScript', year: 2017, rating: null }

 

 

 

원본 유지: 빈 객체를 사용한 병합


위 문제를 해결하기 위해 첫 번째 매개변수로 빈 객체를 전달해 원본 객체를 보호할 수 있다.

const updatedBook = Object.assign({}, defaults, book); 
console.log(updatedBook);
// { author: 'Joe Morgan', title: 'Simplifying JavaScript', year: 2017, rating: null } 
console.log(defaults); 
// { author: '', title: '', year: 2017, rating: null }

 

 

깊은 복사와 Object.assign의 한계


Object.assign은 **깊은 복사(Deep Copy)**를 지원하지 않는다. 중첩된 객체를 복사하면 객체 참조만 복사되어, 원본 객체의 내부 값이 변경되면 복사된 객체에도 영향을 미친다.

중첩 객체 복사 예시

 
const defaultEmployee = {
  name: {
    first: '',
    last: ''
  },
  year: 0
};

const employee = Object.assign({}, defaultEmployee);
employee.name.first = 'John';

console.log(defaultEmployee.name.first); // 'John' (원본 객체도 변경됨)

 

 

깊은 복사를 위한 해결 방법


중첩된 객체를 복사하려면 각 속성에 대해 별도로 Object.assign을 호출해야 한다.

const employee2 = Object.assign({}, defaultEmployee, {
  name: Object.assign({}, defaultEmployee.name)
});

employee2.name.first = 'Jane';

console.log(defaultEmployee.name.first); // ''
console.log(employee2.name.first); // 'Jane'

 

 

대안: JSON 기반 깊은 복사


간단한 깊은 복사가 필요하다면 JSON.parse와 JSON.stringify를 사용할 수도 있다.

const employee3 = JSON.parse(JSON.stringify(defaultEmployee));
employee3.name.first = 'Alice'; 
console.log(defaultEmployee.name.first); 
// '' console.log(employee3.name.first); // 'Alice'​

이 방법은 객체 내 메서드를 포함하지 않거나, 순환 참조가 없는 경우에만 사용할 수 있다.

 

 

결론


  • 얕은 복사가 필요한 경우 Object.assign을 사용하면 간결하고 직관적이다.
  • 깊은 복사가 필요한 경우 중첩 객체를 직접 복사하거나, 재귀 함수를 작성하거나, lodash 같은 라이브러리를 활용할 수 있다.
  • 객체 병합과 복사의 방식은 상황에 맞게 선택하되, 원본 객체가 손상되지 않도록 주의해야 한다.