JS에서 객체와 배열 복사하기_깊은 복사

holymoly.jun·2021년 6월 4일
2

JavaScript 완전 정복

목록 보기
4/5
post-thumbnail

객체의 얕은 복사와 깊은 복사 _ 깊은 복사편



얕은 복사 시, 문제점

그러나, 위와 같이 메소드를 활용한 복사에는 중첩 객체(Nested Object)에서 문제점이 발생한다.
ex) 중첩 객체 : 객체 안에 객체, 객체 안에 배열

let obj = { name : 'jun', child : [0,1,2] };
let copy = Object.assign({}, obj);

copy.name = 'kim'
copy.child[0] = 'new';

console.log(obj); // { name : 'jun', child : ['new',1,2] };
console.log(copy); // { name : 'kim', child : ['new',1,2] };
console.log(obj===copy, obj.child===copy.child); // false true

메소드(assign)을 활용하여 객체를 복사할 경우에는 얕은 복사가 되므로, 중첩된 부분인 깊은 곳까지는 복사가 되지 않는 것이다.
따라서, 객체 obj 와 객체 copy 는 다른 곳을 참조하고 있기 때문에 obj===copy 는 false값이 출력되지만, 배열 obj.childcopy.child 는 같은 곳을 참조하고 있기 때문에 true값이 출력된다.


1. 메소드를 활용한 깊은 복사하기

얕은 복사 메소드를 활용하여, 깊은 복사가 가능하다. 위의 예시에서 child 는 배열이므로 해당 중첩 계층에서 배열 메소드를 활용하여, 재복사를 해주면 된다.

let obj = { name : 'jun', child : [0,1,2] };
let copy = Object.assign({}, obj); // 얕은 복사
copy.child = obj.child.slice(); // 깊은 복사

console.log(obj===copy, obj.child===copy.child); // false false

copy.name = 'kim'
copy.child[0] = 'new'; 

console.log(obj); // { name : 'jun', child : [0,1,2] };
console.log(copy); // { name : 'kim', child : ['new',1,2] };

그러나, 이러한 메소드는 obj.child.slice(); 와 같이 중첩 시점에서 명시적으로 복사 메소드를 적어주어야 한다는 단점이 존재한다.


2. 재귀 함수로 깊은 복사 직접 구현하기

중첩 객체의 중첩 시점이 어디인지 정확히 파악이 어렵다면, 직접 함수를 만들어주는 방법이 있다. 재귀함수를 통해 최하단 계층까지 내려가 직접 복사를 해주면된다.

const copyObj = (obj) => {
    let copy = {};

    if (typeof obj === 'object' && obj !== null) {
        for (let a in obj) {
            if (obj.hasOwnProperty(a)) {
              	//hasOwnProperty : 객체가 특정 property를 가지고 있으면 true 반환
                copy[a] = copyObj(obj[a]);
            }
        }
    } else {
        copy = obj;
    }
    return copy;
};

그러나, 위의 재귀 함수는 객체의 객체는 깊은 복사가 가능하지만, 객체의 배열은 복사가 불가능하다. 예를 들면 이렇다.

let obj = { name : 'jun', child : ['a','b','c'] };
let copy = copyObj(obj);

console.log(copy); // { name : 'jun', child: {0: "a", 1: "b", 2: "c"} }

해당 함수는 객체의 객체에 최적화된 함수이므로 객체의 배열객체의 객체로 변환 시켜버렸다. 그렇다면, 객체와 배열에 모두 대응할 수 있는 방법은 없을까?


3. JSON 객체의 메소드를 이용한 깊은 복사하기

  • JSON.parse() : JSON 문자열 -> 객체
  • JSON.stringify() : 객체 -> JSON 문자열
let obj = { name : 'jun', child : ['a','b','c'] };
let copy = JSON.parse(JSON.stringify(obj));

console.log(copy); // { name : 'jun', child: ["a", "b", "c"] }

깊은 복사이므로 당연히 복사본에 대한 수정 시, 원본의 불변성은 유지시킬 수 있다.

const obj = {
  name: 'jun',
  child : {
    first : 'kim',
    second : 'young',
  }
};

let copy = JSON.parse(JSON.stringify(obj));

console.log(obj === copy); // false;

copy.child.first = 'update';

console.log(obj); // ...first : 'kim' , ...
console.log(copy); // ...first : 'update' , ...

그러나, JSON 객체 메소드 역시 단점이 존재한다. JSON 객체 메소드는 함수와 정규표현식 등 특정 데이터 타입에는 지원이 되지 않는다.
또한, JSON 객체 메소드 자체가 매우 안좋은 성능을 보이고 있으므로 코딩테스트와 같은 효율성을 따질 때는 기피하는 것이 좋다.

4. 남이 만든 것 사용하기

이 방법이 가장 속 편하다... 간단할 것이라고 생각했던 객체 복사는 많은 알고리즘과 예외 처리가 필요하다. 모든 데이터 타입과 모든 계층형 구조에 대응할 수 있는 커스텀 함수를 만들기란 쉽지 않다.
그래서, 남이 잘 만들어놓은 검증된 객체 복사 라이브러리를 사용하는 것이 에러 확률을 줄일 수 있다.



출처

profile
Front-End Developer

0개의 댓글