자바스크립트 객체 복사하기

ddalpange·2019년 1월 7일
4

자바스크립트에서 객체를 복사하는 방법을 설명하는 글.

시작하기 전에

A코드

const a = 1;
const b = a;
b = 2;
console.log(a, b);

B코드

const a = { p : 1 };
const b = a;
b.p = 2;
console.log(a.p, b.p);

A코드와 B코드 두가지의 코드가 있다.
두 코드 모두 b에 a를 대입하였다.라고 생각하는가?
혹은
두 코드 모두 b에 a를 복사하였다라고 생각하는가?

결론부터 말하자면 틀렸다.
자바스크립트는 불변형의 데이터를 선언할 때 포인터와 값 모두 생성하지만,
오브젝트(배열)을 생성할 때에는 메모리 절약을 위해 포인터만 새로 할당할 뿐이다.

즉 A코드에서는 b에 a를 복사하였다.가 맞는것이고
B코드에서는 b에 a를 대입하였다.가 맞는 해석이 된다.
이제 B코드에서 b에 a를 복사하였다가 성립하도록 해보자.

1. Object.assign()을 이용

function cloneObject(obj) {
  return Object.assign({}, obj);
}

Object.assign은 첫번째 인자로 들어오는 객체에 두번째 인자로 들어오는 객체의 프로퍼티들을 차례대로 덮어쓰기하여 반환하는 메소드이다.
여기서 주의할 점은 Object.assign은 프로퍼티들에 대한 참조를 덮어쓰기하기 때문에, 오브젝트 안에 오브젝트 또는 배열이 있을 경우 복사가 아닌 참조를 하게된다.
즉 객체를 얕은 복사(Shallow Copy)한다.

2. JSON 객체의 메소드를 이용

function cloneObject(obj) {
  return JSON.parse(JSON.stringify(obj));
}

JSON.stringify는 자바스크립트 오브젝트를 스트링 포멧으로 변환하는 메소드이다.
JSON.parse는 스트링 포멧을 자바스크립트 오브젝트로 변환하는 메소드이다.

스트링으로 변환하였다가 다시 오브젝트로 변환하기 때문에 이전 객체에 대한 참조가 없어지지만 JSON 메소드 자체가 성능면에서 다른 방법에 비해 굉장히 느리기 때문에 주의해야한다.
이 방법은 객체를 깊은 복사(Deep Copy)한다.

3. 자바스크립트 재귀 사용

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

오브젝트의 프로퍼티들을 순회하여 빈 오브젝트에 더한다. 그 과정에서 원본 오브젝트의 프로퍼티가 오브젝트일 경우 재귀적으로 함수를 실행한다.
이 방법은 객체를 깊은 복사(Deep Copy)한다.

4. Immutable.js 사용

import { Map } from 'immutable';

const map = Map({a : 1});
const newMap = map;
newMap.set('a', 2);
console.log(map.get('a'));
console.log(newMap.get('a'));

페이스북에서 만든 오픈소스 라이브러리이다.
Immutable을 쓰게된다면 Array, Map 모두 이뮤터블하게 쓸 수 있게된다.
객체의 내부 값을 변경해도 원본 객체의 값은 변화하지 않고 새로운 객체를 배출한다는 뜻이다.
사용법이 비교적 간편하지만 처음 보는 사람일 경우 적응하는데 어려움이 있을 수 있다.

여담이지만 이 라이브러리를 사용할 때 로그를 찍을려면 toJS메소드를 사용하여 순수 자바스크립트 객체로 변환해야하는데 매번 까먹어서 불편했다 :(

5. Spread 문법 사용

function cloneObj(obj) {
	return { ...obj }
}

Spread 구문을 사용하면 배열이나 문자열과 같이 반복 가능한 문자를 0개 이상의 인수 (함수로 호출할 경우) 또는 요소 (배열 리터럴의 경우)로 확장하여, 0개 이상의 키-값의 쌍으로 객체로 확장시킬 수 있다.
즉 객체를 얕은 복사(Shallow Copy)한다.

정리하며

사실 유지보수나 신규개발을 하면서 객체의 딥카피가 필요한 경우는 많이 없었다.
대부분 스왈로카피로 해결할 수 있으며 딥카피는 피하는것이 더 빠르고 직관적이기 때문이다.

주의할 점은 단순히 =를 통해 변수를 대입하는것과 얕은 복사는 엄연히 다르다는 것이다.
=얕은 복사깊은 복사의 차이만 알아가도 성공한것이 아닐까 ?
이것을 모르는 개발자들이 적지 않아서 조금 놀랐다.

profile
잡부입니다.

7개의 댓글

comment-user-thumbnail
2019년 1월 7일

Immer도 괜찬겟네요 ㅎㅎㅎ

답글 달기
comment-user-thumbnail
2019년 1월 7일

스프레드 문법을 이용한 방법도 있어요 😀

1개의 답글
comment-user-thumbnail
2019년 1월 9일

얕은 복사는 shallow copy 입니다 ㅎㅎ;;;

  • spread 연산자도 얕은 복사입니다.
const a = { child_obj: { c: 123 }, num: 1, str: 'aaa' };

const b = { ...a };

console.log(a === b); // false
console.log(a.child_obj === b.child_obj); // true
1개의 답글
comment-user-thumbnail
2019년 11월 12일

감사합니다. vuex data를 바로 건들인다고 자꾸 에러가 나서 고민하다가 벨러퍼트님이 언제 한번 말씀하셨던게 기억나서 해보니 해결되었습니다. ^^* 무식하게 JSON 메쏘드를 쓰긴 했든데, 내부적으로 가볍게 쓸 거니까..
다음에는 Immer를 염두에 둬야 겠네요.. ㅎㅎ

답글 달기
comment-user-thumbnail
2020년 2월 26일

좋은 글 잘 읽었습니다.
프론트 개발자를 준비 중인 퍼블리셔입니다. 실례가 안된다면 개인블로그에 기록해도 될까요?
출처를 필히 밝히며, 상업적 용도는 없는 개인 공부용 블로그입니다.

답글 달기