본문 바로가기
2. 우당탕탕 개발자/2-2. 상세 노트

얕은 복사, 고차원의 깊은 복사

by Little Monkey 2021. 7. 31.
반응형

깊은 복사 vs 얕은 복사

우선 자바스크립트의 자주 쓰이는 타입들을 살펴보자. 복사의 차원에선 자바스크립트의 타입을 참조형기본형으로 나눌 수 있다. 값과 주소로 이루어진 참조형과 값 자체인 원시형 기본형 타입으로 나누어진다.

참조형 (Reference type) : 값 - 주소로 이루어짐.

  • object (array 포함)
  • function

기본형 (Primitive type) : 값 자체

  • number
  • string
  • boolean (true/false)
  • null / undefined
  • symbol

얕은 복사(Shallow Copy)

얕은 복사는 '주소'만 복사한다. 기본 타입은 주소가 없기 때문에 그 값을 복사한다. 문제는 참조형이 주소만 복사하기 때문에 원본이 변하면, 복사된 값도 변화한다는 것이다. 반대의 경우도 마찬가지로 적용된다.

 

let num = 1;
let arr = [1];
let newNum = num;
let newArr = [...arr];

newNum = 2,
newArr[0] = 2;

console.log(num, newNum); //1, 2
console.log(arr, newArr); // [2], [2]

 

깊은 복사(Deep Copy)

깊은 복사는 '값-주소'를 동시에 복사한다. 방법은 Array.slice() , Object.assign({}, 복사하고자하는 객체) 를 이용하면 된다. 원본 또는 복사본이 변화해도 서로에게 영향을 미치지 않는다.

 

let arr = [1];
let arrS = [...arr];  // 얕은 복사
let arrD = arr.slice();  // 깊은 복사
let arrD1 = Object.assign([], arr); // 깊은 복사

let arr[0] = 2;
console.log(arr, arrS, arrD, arrD1) 
// [2], [2], [1], [1]

 

이 역시 문제가 있다. 깊은 복사는 그 복사를 한 깊이에서만 깊은 복사를 한다는 것이다. 위의 두가지 매소드는 원소 안에까지 깊이 돌아가며 깊은 복사를 해주지 않는다는 점이다.

 

let arr = [1 , 2, ['a']];
let newArr = arr.slice();

newArr[1] = 3;
newArr[2][0] = 'b';

console.log(arr);  //[1, 2, ['b']]
console.log(newArr); //[1, 3, ['b']]

 

상단의 예제를 보면 2 depth로 구성된 배열의 경우, 2 깊이의 배열은 깊은 복사가 되지 않고, 여전히 원본을 참조하고 있다. 따라서 사본의 값이 변화할 때 원본의 값도 변화했다.

 

이를 방지하기 위해 JSON.stringify() 를 이용할 수 있다. JSON으로 해당 참조형을 통제로 변환한 뒤에 이를 다시 JSON.parse() 해주는 것이다. 혹은 LodashRamada 패키징이 모든 깊이의 깊은 복사를 구현해놓았다. 자세한 설명은 다음의 미디엄을 참고할 것

 

깊이를 파고 들어가면서, 참조형 타입이 있으면 깊은 복사로 그 때 마다 복사해주면 되지 않을까? 2 깊이의 배열을 가지고 여러가지 실험을 해보았다.

 


 

map(), forEach() 를 이용해서 원소의 참조형도 깊은 복사를 해보자.

let arr = [[1], [2]];
let newArr = arr.slice();

newArr.map(el => { //forEach도 같은 결과
  let copy = el.slice();
  copy.push(1); //push 는 mutable 하다.
  console.log(copy) // [1,1] [2,1] 잘 출력한다.
     return copy;
});

console.log(arr, newArr)
// 그러나 결과는[[1],[2]], [[1],[2]]

 

for...of 를 이용해본다.

let arr = [[1], [2]];
let newArr = arr.slice();

for(let el of newArr) {
  let copy = el.slice();
  copy.push(1); //push 는 mutable 하다.
  console.log(copy) // [1,1] [2,1] 잘 출력한다.
     el = copy.slice(); 
  // 이부분이 잘못인가..? el = copy해도 결과는 다르지 않았다. 
}

console.log(arr, newArr)
// 그러나 결과는[[1],[2]], [[1],[2]]

 

for 문을 이용해본다.

let arr = [[1], [2]];
let newArr = arr.slice();

for(let i = 0; i < arr.length; i++) {
  let copy = newArr[i].slice();
  copy.push(1); //push 는 mutable 하다.
  console.log(copy) // [1,1] [2,1] 잘 출력한다.
     newArr[i] = copy 
}

console.log(arr, newArr)
// 드디어 [[1],[2]], [[1,1],[2,1]]

 

왜 위의 2가지는 안되고, 아래의 for문은 가능한지 아직도 의문이다. 혹시나 이 포스트를 발견하신 분... 댓글 좀 달아주세요...🙌

반응형

댓글