[JavaScript] 깊은 복사와 얕은 복사

Q1. 스크립트에서 변수의 복사시 발생되는 문제

먼저 스크립트에서 객체를 복사하여 새로운 변수에 담고 사용하다보면 서로 동기화가 되어있는지 서로가 동일하게 변경되는 현상을 확인한 적이 있다. 이러한 현상은 자바스크립트에서 얕은 복사를 했기 때문에 발생되었다고 한다.

- 깊은 복사와 얕은 복사란 뭘까

원시 타입 (Primitive Type)
number, string, boolean, null, undefined, symbol.
위 타입은 데이터 생성 및 복사시 새로운 메모리 공간에 독립적으로 메모리 재할당하여 저장된다.
때문에 복사나 수정시 기존의 값이 변하지 않는다.

참조 타입 (Reference Type)
원시 타입을 제외한 나머지 ( Array, Function, Object ).
원시 타입과 같이 메모리에 직접 접근하지 않고 Array, Function, Object와 같이 간접적으로 메모리에 접근할 수 있는 기능들을 할당하여 변수에 메모리 주소를 저장한다. 때문에, 복사나 수정시 메모리 주소 자체가 복사되어 기존 값이 변하는 경우가 발생한다.


깊은 복사
(Deep Copy)
독립적인 메모리에 할당. 
쉽게 말해, 복사나 수정시 기존 값이 변하지 않는 것

얕은 복사 (Shallow Copy)
객체를 복사할 때 값 자체를 복사하는 것이 아니라 힙 메모리의 주소를 복사하는 것이다.
쉽게 말해, 복사나 수정시 기존 값이 변하는 것

정리를 하자면, 객체, 배열, 함수는 복사할 때 얕은 복사의 문제가 발생할 수 있다는 점이다.문제를 해결하는 방법을 알기전에 위 내용에 대해 이해하기 쉽게 예제를 확인하자.
 

/**
  * str1은 원시 타입이다. 때문에 깊은 복사가 발생한다.
  * str2에 str1을 복사하고 난뒤에 str2의 값을 변경하더라도
  * str1과 str2의 값은 따로 저장되어 출력되는 것을 확인할 수 있다.
  */
let str1 = 100;
let str2 = str1;

console.log(str1); //100
console.log(str2); //100

str2 = 200;
console.log(str1); //100 기존 값 유지됨
console.log(str2); //200 


/**
  * obj1는 참조 타입이다. 때문에 얕은 복사가 발생한다.
  * obj2에 obj1을 복사하고 난뒤에 obj2의 값을 변경하면
  * obj1와 obj2의 값이 동일하게 출력되는 것을 확인할 수 있다.
  */
let obj1 = [1, 2, 3, 4];
let obj2 = obj1;

console.log(obj1); //[1, 2, 3, 4];
console.log(obj2); //[1, 2, 3, 4];

obj2[0] = 0;
console.log(obj1); //[0, 2, 3, 4]; 기존 값 유지안됨
console.log(obj2); //[0, 2, 3, 4];

if(obj1 === obj2){
 console.log("주소까지 아예 동일한 얕은 복사상태야");
}

 
 

- 참조 타입의 깊은 복사 처리 방법

그렇다면, 참조 타입을 깊은 복사하기 위한 방법들이 몇 가지 있어서 아래와 같이 정리를 하였다.

참조 타입의 깊은 복사 처리 방법.

1차원
: 원시형, [, , , ], {}

- Lodash의 deepclone 라이브러리 이용 (ex. _.cloneDeep(복사할 값))
- spread연산을 이용 (ex. x={a:'hello'};  y={...x, a:'world'})
- 객체가 원시형으로만 이루어진 경우 (ex. x={a:1, b:2}) .
(단, x={a:1,[3])과 같이 참조형이 참조형이 포함되면 안됨)
- Object.assign( {}, 복사할 객체 )를 이용.
(단, 첫번째 파라미터를 {}로 하지않으면 파라미터의 첫번째 객체가 얕은 복사된다.)


N차원 : [{}, {}, {}....], [[], [], []]
JSON.parse() 와 JSON.stringify() 이용 (ex. JSON.parse(JSON.strinfify(객체))
(JSON.stringify를 통해 하나의 문자열 원시타입으로 참조를 모두 제거하고. JSON.parse를 통해 다시 원래의 객체 타입으로 변경하는 원리이다. function타입일때는 undefinded가 나오고 속도가 느리지만 가장 간단하게 사용된다)


참조 타입의 깊은 복사 처리 방법은 참조 타입일지라도 내부는 원시 타입으로 이루어져있거나, 참조를 모두 제거하고 다시 원래의 객체 상태로 돌려놓거나의 처리 방법을 인지하고 있으면 된다.
결론적으로는 내가 사용하는 변수의 타입에 따라 복사할 때, 값의 주소 자체가 복사되어 얕은 복사가 발생되는 문제를 알고 작업을 하는게 좋다.
 
 

Q2. 스크립트에서 HTML 태그 복사시 발생되는 문제

위의 경우는 스크립트의 변수에서 발생되는 문제라면,
아래에 다룰 내용은 HTML Element의 태그를 복사할때도 기존 태그에 문제가 발생되는 것을 확인하자.
아래의 예제 코드와 주석을 통해 안되는 사례들을 먼저 정리하였다.

<head>
<script>
$(document).ready(function(){
   //실패1: div1이 div3으로 복사가 아니라 이동되었다. (얕은 복사가 됨을 의미)
   //var area = $("#div1");
   //$("#div2") = area;   
   

   //실패2: div1이 div3으로 복사가 아니라 이동되었다. (얕은 복사가 됨을 의미)
   //var area = $("#div1");
   //$("#div2").html(area);      
});

</script>
</head>
<body>
 <div id="div1">
 	<h1>1번째 DIV영역</h1>
 </div>
 <div id="div2">
 </div>
</body>

따로 var area이라는 변수에 담아두었는데도, 기존의 div1은 사라지고 div2에 붙여넣어지는 것을 확인할 수 있다. 
때문에 스크립트에서 HTML 태그를 복사할 때도 깊은 복사와 얕은 복사가 있다는 것을 확인할 수 있다.
아래와 같은 방법으로 깊은 복사 방식으로 생성된 변수를 이용하면 독립적으로 사용할 수 있다.
 

- element 태그의 깊은 복사 방법

자바스크립트로 깊은 복사와 얕은 복사 지정하기
- 깊은 복사 : var a = document.getElementById("ID").cloneNode(true);
(단, false는 얕은 복사이다)

Jquery를 이용하여 깊은 복사 사용하기.
- 깊은 복사 : var a = $("#복사할객체ID").clone();

 

Q3. 결론적으로