티스토리 뷰

얕은 복사메모리 주소값을 복사하는 개념이고, 깊은 복사는 새로운 메모리 공간을 확보해 완전히 복사하는 개념이다. 얕은 복사와 깊은 복사는 데이터 저장 방식과 깊은 관련이 있다.

데이터 저장 방식

let A = 컵;
let C = 우산;
let D = 우산;
let B = 컵 + 우산;

변수 선언이 일어나면 컴퓨터의 메모리에서 비어있는 공간 하나를 확보한다. 그 공간에 이름을 지정하고, 데이터를 또 다른 공간에 저장한 후, 데이터가 담긴 공간 정보를 저장한다. 다소 복잡해보이는데 왜 이렇게 할까?

  1. 데이터 변환을 자유롭게
  2. 메모리 관리를 더욱 효율적으로 하기 위해서

다음과 같이 사물함에 물건을 저장하는 그림에 비유해보았다. (실제 변수저장 메커니즘은 변수영역과 데이터영역, 메모리주소, 식별자와 값 등의 개념이 있다.)

  1. 데이터 변환을 자유롭게
    만약 사물함에 주소 정보를 저장하는 것이 아닌, 물건을 직접 저장한다고 한다면 A에 처음에 컵을 담았다가 우산으로 바꾸려고 할 때, 크기가 작아서 안들어갈 것이다. A사물함 크기를 직접 늘리는 작업이 필요하고 만약 인접 사물함에 물건이 담겨있다면 밀리거나 변형되는 등의 영향을 끼칠 것이다. 아예 새로 다른 칸에 담는 방식도 생각할 수 있는데 이는 이어질 2번의 메모리 관리의 측면에서 비효율적이다. 실제 방식은 다른 칸 O에 우산을 담고 'I로 가세요'를 'O로 가세요'로 주소 정보를 수정하여 A는 컵에서 우산으로 변경된다.
  2. 메모리 관리를 더욱 효율적으로 하기 위해서
    데이터를 직접 저장하는 방식은 C, D, E, F, ... 등 무수한 500개의 변수에 '우산'이라는 동일한 값을 넣어야 하는 경우에 500개의 큰 사물함에 우산을 각각 다 담아야 할 것이다. 공간이 낭비된다. 실제 방식은 O사물함에 우산을 담고 500개의 변수들이 O사물함을 가리킨다. 우산처럼 큰 사물함이 아닌 주소 정보만 담을 수 있는 작은 사물함들을 사용해도 같은 결과를 가질 수 있다.
    또는, B에 컵 + 우산을 저장하려고 할 때 이미 컵과 우산이 들어있는 칸이 있는데 별도로 큰 사물함에 컵과 우산을 함께 담아서 생성해줘야 할 것이다. 실제 방식은 별도 생성할 필요없이 컵과 우산이 들어있는 칸 모두의 주소를 저장한다.

 

원시자료형과 참조자료형의 저장

let a = 'hi';
a = 'hello';

위의 그림은 해당 코드의 과정을 그린 것이다. 원시자료형의 값 @5002 또는 @5003 을 보니 값이 담겨있다.

const obj = {
  a: 'hi',
  b: 10
};
obj.a = 'hello';

참조자료형은 값 @5004를 보니 또 다른 곳 @7102@7103을 참조하고 있다.

 


 

얕은 복사

얕은 복사는 한 단계 아래까지만 복사하는 것이다. 위 원시자료형과 참조자료형의 저장에서 a의 값(@5002 @5003) 또는 obj의 값(@5004)을 복사한다.

// 원시자료형의 얕은 복사
let a = 'hi';
let b = a;
a = 'hello';
console.log(a, b); // 'hello' 'hi'

원시자료형의 경우,

  1. b가 a의 값 @5002를 복사하여 가진다.
  2. a는 값을 @5003으로 교체한다.

a와 b가 복사 한 후에 독립적이다. ctrl+C ctrl+V한 것 같은 결과이다.

// 참조자료형의 얕은 복사
const obj1 = {
  a: 'hi',
  b: 10
}
const obj2 = obj1;
obj2.a = 'hello';
console.log(obj1.a, obj2.a); // 'hello' 'hello'

참조자료형의 경우,

  1. obj2는 obj1의 값 @5004를 복사하여 가진다.
  2. obj2.a의 값 @5002@5003으로 교체한다.
  3. 여전히 obj1과 obj2의 값은 @5004이고 데이터 @7102~?를 가지고(참조하고) 있다.

복사본의 값을 바꾸면 원본의 값도 같이 바뀐다. obj2가 복사한 것은 값이라기보다는 @7102~@7103을 참조하고있음을 복사했다. 같은 것을 참조하게 되었기 때문에 복사보다는 공유라고 볼 수 있다. obj2.a를 바꾸었는데 이들은 공유되고 있기 때문에 obj1.a도 같이 바뀐다.

= (할당, assign)

let a = 100;
let b = a; // 얕은 복사
a = 50;
console.log(a===b); // false

const obj1 = {
  c: 'c',
  d: 'd'
}
const obj2 = obj1; // 얕은 복사
obj2.c = 'zzz';
console.log(obj1===obj2); // true

참조자료형의 경우 원본이 같이 변경된다.

Array.slice()

const arr1 = [ 1, 2, 3, [4, 5] ];
const arr2 = arr1.slice();
arr2[0] = 0;
arr2[3][0] = 0;
console.log(arr1); // [ 1, 2, 3, [0, 5] ]
console.log(arr2); // [ 0, 2, 3, [0, 5] ]

1단계 레벨까지는 잘 복사가 되지만 중첩된 구조에서는 원본이 영향을 받는다.

Object.assign()

const obj1 = {
  a: 0,
  b: 0,
  c: {
    d: 0
  }
};
const obj2 = Object.assign({}, obj1);

obj2.a = 1;
console.log(obj1); // { a: 0, b: 0, c: { d: 0 } }
console.log(obj2); // { a: 1, b: 0, c: { d: 0 } }

obj2.c.d = 1;
console.log(obj1); // { a: 0, b: 0, c: { d: 1 } }
console.log(obj2); // { a: 1, b: 0, c: { d: 1 } }

복사한 객체 자체는 깊은 복사가 되지만, 내부의 객체에 대해서는 얕은 복사가 된다. 다음의 ...(전개구문)과 동일하게 1레벨 깊이에서만 효과적인 복사 방법이다.

... (전개구문, spread operator)

const arr1 = [ 1, 2, [ 0 ] ];
const arr2 = [ ...arr1 ];

arr2[0] = 0;
console.log(arr1); // [ 1, 2, [ 0 ] ]
consolt.log(arr2); // [ 0, 2, [ 0 ] ]

arr2[2].push(1);
console.log(arr1); // [ 1, 2, [ 0, 1 ] ]
consolt.log(arr2); // [ 0, 2, [ 0, 1 ] ]

Array.slice()와 다른 결과가 나왔다. Object.assign()과 동일하게 1레벨에서만 효과적으로 복사할 수 있다.

 


 

깊은 복사

복사를 하는 것은 기존의 값과 독립적으로 영향이 없도록 하기 위함일 것이다. 객체가 얕은 복사로도 복사의 기능을 할 수 있다면 문제가 없을 것이다. 그러나 JavaScript가 이런 원리로 작동하므로 이곳저곳에서 원본데이터를 동시에 변형하여 프로그램이 의도한대로 동작하지 않을 수 있다. 깊은 복사를 해주어야 한다.

재귀함수 구현 (recursive function)

function isCopyObj(origin) {
    let result = {};

    for (let key in origin) {
      if (typeof origin[key] === 'object') {
          result[key] = isCopyObj(obj[key]);
      } else {
          result[key] = origin[key];
      }
    }

    return result;
}

const obj1 = {
  a: 0,
  b: 0,
  c: {
    d: 0
  }
};
const obj2 = isCopyObj(obj1);

위 함수 isCopyObj()로 깊은 복사를 할 수 있다. 서로 참조관계가 없는 다른 객체이다.

JSON객체 이용

const obj1 = {
  a: 0,
  b: 0,
  c: {
    d: 0
  }
};
const obj2 = JSON.parse(JSON.stringify(obj));

JSON객체의 copyTarget = JSON.parse(JSON.stringify(target))를 이용한다. obj1과 obj2는 서로 참조관계가 없는 다른 객체이다.

 

이전 블로그(velog.io/@hahagarden)에서 이전해온 글입니다. 

 

반응형
댓글