JS | 데이터 할당과 복사 총정리

Autumn·2020년 12월 17일
3

JavaScript

목록 보기
17/18
post-thumbnail
post-custom-banner
  • 코어자바스크립트 책을 읽고 정리한 내용입니다. 🤓

얕은 복사? 깊은 복사? 도대체 뭐야?

코딩을 하다 보면 얕은 복사, 깊은 복사라는 말을 들어봤을 것이다. MDN 사이트에서도 뭐뭐를 얕게 복사하여 어쩌구저쩌구 라는 말을 심심찮게 볼 수 있다. 이 요상한 말들은 무슨 뜻일까 함께 알아보자!

복사를 이해하기 위해서는 변수에 데이터가 어떻게 할당되는지를 우선 알아야 한다. 데이터 할당은 기본형 데이터냐 참조형 데이터냐에 따라서 약간 다르다.


1. 데이터 타입의 종류

  • 기본형 데이터 (Primitive type)
    Number, String, Boolean, null, undefined, Symbol
  • 참조형 데이터 (Reference type)
    Object - Array, Function, Date, RegExp, Map/WeakMap, Set/WeakSet

2. 데이터 할당

데이터 할당을 알아보기 전에 변수와 식별자에 대해 짚고 넘어가보자. 변수란, 변경 가능한 데이터가 담길 수 있는 공간 또는 그릇. 식별자란 그 변수의 이름, 즉 변수명이다. 함수명 또한 식별자에 포함된다. 변수에 어떤 값을 저장할 때 곧바로 변수명과 데이터를 묶어서 저장할 것만 같지만 사실은 그렇지 않다. 기본형 데이터든 참조형 데이터든 기본적으로 '주솟값'을 저장한다.


2-1. 기본형 데이터

let a = 2;

변수 a를 선언하고 a에 number 타입, 즉 기본형 데이터인 2를 할당했다. 메모리 영역에서는 어떤 일이 일어날까? 이해를 위해 메모리 영역이 변수 영역과 데이터 영역으로 나눠진다고 가정해보자. (실제로는 이렇게 나뉘어 있지 않다.)

대략적인 다이어그램으로 보면, 1003 주소에 변수의 이름(a)와 값(2)이 모두 들어있는 것이 아니라, 변수의 이름(=식별자, a)과, 2라는 값이 저장된 '주솟값'이 저장되어 있다. 조금 더 자세히 말자하면, 1003에서 주솟값을 저장하기 전에 우선 데이터 영역에 2라는 값이 있는지 찾아보고, 없으면 비어있는 공간에 2를 저장, 그 주솟값을 1003에 저장하는 것이다.

그냥 a와 2를 1003 한 곳에 저장하면 편할텐데, 왜 이렇게 복잡하게 저장을 하는 것일까?
그것은 중복된 데이터가 있을 때 메모리를 절약하기 위해서이다. a와 같은 값을 가지는 b라는 변수를 새로 만들어보자.

let a = 2;
let b = 2;

메모리 영역에서는 같은 값을 각각 다른 메모리에 할당하는 것이 아니라, 주솟값을 공유하여 메모리를 절약한다. 즉 let b = 2 에서는 1. 비어있는 @1004에 변수명(식별자) b를 저장하고, 2. 데이터 영역에 2라는 값이 있는지 찾아본다. 3. @50042가 저장되어 있으므로 @1004의 값에 @5004를 써놓는 것이다. 중복된 값이 많아질수록 절약되는 메모리도 많을 것이다.

추가로 알아두기

만약 a의 데이터를 변경하고 싶다면, @5004 에 저장된 값을 직접 변경하는 게 아니라 무조건 새로 만들어 별도의 공간에 저장한다. 물론 이 과정에서 b가 그랬던 것처럼 다른 변수의 값과 중복된 값이라면 @1003에 그 주솟값을 저장할 것이다. 즉, 데이터 영역의 어떤 주소에 저장된 기본형 데이터 (primitive type) 는 바뀌지 않는다. 이것을 불변성이라고 한다.

또한 이렇게 값을 바꾸게 되어 아무도 그 데이터 주소를 참조하지 않으면 해당 데이터는 Garbage Collection 의 대상이 된다.


2-2. 참조형 데이터

객체, 배열, 함수 등 참조형 데이터의 할당 원리도 기본형 데이터와 같다. 다만 중간 과정이 하나 더 생긴다. 다음과 같은 객체를 메모리에 어떻게 저장하는지 함께 살펴보자.

let obj1 = {
  a: 1,
  b: 'bbb'
};

객체가 어떻게 생겨먹었는지 보면, 1. 객체의 이름 (변수명, 식별자) 2. {} 안에 들어있는 내용들 덩어리 3. key-value 쌍 (이 예시에서는 value가 모두 기본형 데이터이다.) 이렇게 나눠볼 수 있겠다. 2-1. 기본형 데이터 와 비교했을 때, 저장해야 할 데이터의 양이 늘어났음을 알 수 있다.

중간과정이 하나 생긴다는 의미는 바로, key-value 쌍 들이 저장되어 있는 주솟값을 중간에 한 번 저장한다는 뜻이다. 엥?? 그게 무슨 말이냐고? 이해를 위해 메모리 영역이 변수 영역, 데이터 영역, 그리고 하나 더 추가해서 객체의 변수 영역으로 나누어져 있다고 가정해보자.

그림을 보면 @7103 -> @5007, @7104 -> @5008 이렇게 가리키는 것은 2-1. 기본형 데이터에서의 과정과 같다는 것을 알 수 있다. 처음부터 무슨 일이 일어나는지 보자.

  1. 우선 변수 영역의 빈 공간 @1005 를 확보하고, 이름을 obj1 으로 지정한다.
  2. 임의의 데이터 저장 공간 @5005 에 데이터를 저장하려고 보니, 데이터 여러개로 이루어진 객체 덩어리이다. 이 객체의 프로퍼티를 하나씩 저장하기 전에, 프로퍼티들을 저장할 별도의 변수 영역을 마련하고, 그 영역의 주소 @7103 ~ ?@5005 에 저장한다. 즉, 이 덩어리를 어디에 저장할 것인지, 덩어리가 저장될 주솟값을 중간에 한 번 저장하는 것이다.
  3. 여기서부터는 2-1. 기본형 데이터 와 같은 방법으로 진행된다. @7103, @7104 에 각각 a, b라는 이름을 저장한다.
  4. 데이터 영역에서 1을 검색, 1이 없으므로 새로 공간을 확보해서 @5007에 저장, 이 주소를 @7103 에 저장. 데이터 영역에서 'bbb' 검색, 'bbb' 가 없으므로 새로 공간을 확보해서 @5008 에 저장, 이 주소를 @7104 에 저장.

만약 여기서 obj1.a 의 값을 바꾸면 어떻게 될까? 이 때는 변수 obj1 이 바라보는 주소는 바뀌지 않는다. 프로퍼티의 값이 바뀌는 경우에는 객체의 변수 영역에서부터 처리를 한다.

let obj1 = {
  a: 1,
  b: 'bbb'
};
obj1.a = 4;

2-1. 기본형 데이터 에서 값을 바꿨던 것처럼, 데이터 영역에서 숫자 4 가 존재하는지 확인 후, 없으므로 새로 공간을 확보해서 @5009 에 저장, 이 주소를 @7103 에 저장한다. @5007 의 데이터는 아무도 참조하지 않으므로 Garbage Collection 의 대상이 된다. 이 내용을 그림으로 나타내면 아래와 같다.

추가로 알아두기

@7103 ~ ? 에서 물음표는 뭘까?
객체의 프로퍼티들을 저장하기 위한 메모리 영역은 크기가 정해져 있지 않고 필요한 시점에 동적으로 확보한다는 뜻이다. (이거는 사실 무슨 말인지 잘 이해가 안됨)

중첩 객체 (nested object)

참조형 데이터의 프로퍼티에 다시 참조형 데이터를 할당하는 경우를 말한다. 다음 예시를 보고 메모리 영역에 어떻게 할당이 될지 각자 생각해 보시길...

let obj = {
  x: 3,
  arr: [3, 4, 5]
};

약간의 힌트를 드리자면, 위에서 했던 예시에서 객체 @5005의 변수 영역을 따로 만들어준 것처럼, 배열 @뭐시기의 변수 영역을 따로 또 만들어서 하면 된다. 맨 마지막에 기본형 데이터를 할당해줄 때 우선 데이터 영역에서 그 값을 찾아본 뒤 없으면 새로운 공간에 할당한다는 것을 잊지 마시길! 귀찮아서 못그리겠당 ㅎㅎㅎ


3. 데이터 복사

얕은 복사 : 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사한다는 뜻. 즉, 해당 프로퍼티에 대해 원본과 사본이 모두 동일한 참조형 데이터의 주소를 가리키게 된다. 즉!! 사본을 바꾸면 원본도 바뀌고, 원본을 바꾸면 사본도 바뀐다.

< 글 아직 미완성... >

profile
한 발짝씩 나아가는 중 〰 🍁 / 자잘한 기록은 아래 🏠 아이콘에 연결된 노션 페이지에 남기고 있어요 😎
post-custom-banner

1개의 댓글