CoreJS - 얕은 복사 & 깊은 복사

SANGKU OH·2020년 11월 24일
1

CoreJavascript

목록 보기
2/10
post-thumbnail

얕은복사

바로 아래 단계의 값만 복사하는 방법
중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사한다는 의미!

그러면 해당 프로퍼티에 대해 원본과 사본이 모두 동일한 참조형 데이터의 주소를 가리키게 된다.
사본을 바꾸면 원본도 바뀌고, 원본을 바꾸면 사본도 바뀌게 된다.

중첩된 객체에 대한 얕은 복사

const copyObject = target => {
  let res = {};
  for (let prop in target) {
    res[prop] = target[prop];
  }
  return res;
};

let user = {
  name: 'sangku',
  urls: {
    portfolio: 'http://github.com/sangkuoh',
    blog: 'hhtp://blog.com',
	instagram: 'https://www.instagram.com/539_ku'
  }
};

let user2 = copyObject(user);

user2.name = 'Jung';

console.log(user.name === user2.name); //false

user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio); // true

user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); // true

사본인 user2의 name의 프로퍼티를 바꿔도 user의 프로퍼티는 바뀌지 않았다.
반면, portfolio와 urls의 경우 원본과 사본 중 어느 쪽을 바꾸더라도 다른 한쪽의 값도 함께 바뀐 것을 확인할 수 있다.

즉, 객체에 직접 속한 프로퍼티에 대해서는 복사해서 완전히 새로운 데이터가 만들어지는 반면, 한 단계 더 들어간 urls의 내부 프로퍼티들은 기존 데이터를 그대로 참조하는 것이다.

깊은 복사

내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법이다.
위의 얕은 복사의 문제를 피하기 위해서는 user.urls 프로퍼티에 대해서도 불변 객체로 만들 필요가 있다.
중첩된 객체에 대한 깊은 복사

let user2 = copyObject(user);
user2.urls = copyObject(user.urls);

user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portifolio === user2.urls.portfolio); //false

user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); //false

2번째 줄에서는 urls 프로퍼티에 copyObject 함수를 실행한 결과를 할당했다.
이제 urls 프로퍼티의 내부까지 복사해서 새로운 데이터가 만들어졌으므로 5번째 줄과 8번째 줄에서 값이 서로 다르다는 결과를 얻을 수 있다.

어떤 객체를 복사할 때
객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때, 객체의 프로퍼티 중에서 그 값이 기본형 데이터일 경우에는 그대로 복사하면 되지만, 참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야 한다.

이 과정을 참조형 데이터가 있을 때마다 재귀적으로 수행해야만 비로소 깊은 복사가 되는 것이다..!!

이 개념을 바탕으로 copyObject 함수를 깊은 복사 방식으로 고쳐보자!

객체의 깊은 복사를 수행하는 범용 함수

const copyObjectDeep = (target) => {
  let res = {};
  if (typeof target === 'object' && target !== null) {
    for (let prop in target) {
      res[prop] = copyObjectDeep(target[prop]);
    }
  } else {
    res = target;
  }
  return res;
};

3번째 줄에서 target이 객체인 경우에는 내부 프로퍼티들을 순회하며 copyObjectDeep 함수를 재귀적으로 호출하고, 객체가 아닌 경우에는 8번째 줄에서 target을 그대로 지정하게 만들었다.
이 함수를 사용해 객체를 복사한 다음에 원본과 사본이 서로 완전히 다른 객체를 참조하게 되어 어느 쪽의 프로퍼티를 변경하더라도 다른 쪽에 영향을 주지 않도록 되었다.

깊은 복사를 확인해보자

let obj = {
  a: 1,
  b: {
    c: null,
    d: [1, 2]
  }
};

let obj2 = copyObjectDeep(obj);
obj2.a = 3;
obj2.b.c = 4;
obj2.b.d[1] = 3;

console.log(obj); //{ a: 1, b: { c: null, d: [ 1, 2 ] } }
console.log(obj2); //{ a: 3, b: { c: 4, d: { '0': 1, '1': 3 } } }

추가로 HasOwnProperty 메소드를 활용해 프로토타입 체이닝을 통해 상속된 프로퍼티를 복사하지 않게끔 할 수도 있다!

간단하게 깊은 복사를 하는 법

JSON.stringify()

JSON.stringify()는 객체를 json 문자열로 변환한다. 이 과정에서 원본 객체와의 참조가 모두 끊어진다.
객체를 json 문자열로 변환 후 JSON.parse()를 이용해 다시 자바스크립트 객체로 만들어 주면 깊은 복사가 된다.

🚨이 방법은 사용하기는 쉽지만 다른 방법에 비해 아주 느리다고 알려져있다.

const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const copiedObj = JSON.parse(JSON.stringify(obj));
//string(원시값)으로 변환 후 다시 객체로 만들어 주었다. => 원래값과 복사값이 서로 영향을 받지 않는다.

copiedObj.b.c = 3
console.log(obj.b.c) //2
console.log(copiedObj.b.c) //3

obj.b.c === copiedObj.b.c //false

라이브러리(lodash)

const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const copiedObj = _.cloneDepp(obj);

copiedObj.b.c = 3;

obj.b.c === copiedObj.b.c //false
profile
Prof.Google을 통해 필요한 정보를 이 곳에 insert 🐸

0개의 댓글