원시 자료형과 참조 자료형

Mooby·2023년 3월 2일
0

개념정리

목록 보기
3/12

원시자료형과 참조자료형의 차이점에 대해 알기 전에 메모리 저장에 대한 개념을 알아야 한다.

메모리 저장

예를 들어, a라는 변수에 number타입인 1을 할당해보자.

let a = 1

이렇게 되면 메모리 공간에 a라는 하나의 작은 방이 만들어지고, 그 안에 1이라는 값이 저장된다.

이처럼 우리가 변수를 선언하고, 할당할 때마다 새로운 메모리 공간에 변수와 값이 저장된다.

이번에는 b라는 새로운 변수에 배열을 할당해보자.

b = [1, 2, 3]

메모리공간에 b라는 새로운 방이 만들어지고, 그 안에 [1, 2, 3]이라는 배열이 저장될 것이라고 생각하겠지만, 이것은 틀렸다.

배열이나 객체는 그 값에 대량의 정보를 넣을 수 있기 때문에, 작은 방에 저장하는 것은 효율적이지 않다.. ex) [1, 2, 3, ... 9999999]

그렇기 때문에, 다른 집에다가 배열을 저장해주고, b라는 작은 방에는 배열이 저장되어있는 집의 주소값을 넣어준다.

두 경우, 변수를 불러올 때는

a : a라는 방을 찾고 > 값을 확인해서 불러온다.
b : b라는 방을 찾고 > 주소값을 확인하고 > 주소값으로 된 다른 집에서 배열을 불러온다.

라는 과정을 거치게 된다.

이처럼 메모리 저장하는 방식에 따라서,
a의 타입을 원시자료형, b의 타입을 참조자료형이라고 한다.

원시자료형과 참조자료형

원시자료형의 타입에는 number, string, boolean, undefined, null, symbol 이 있다. 이들은 javascript에 내장되어 그 값이 고정되어 있는 타입이다.

참조자료형은 원시자료형을 제외한 모든 타입이다. array, object, function이 참조자료형에 해당된다.

원시자료형의 특징

  • 원시자료형을 변수에 할당하면, 메모리 공간에 그 값 자체가 저장된다.
  • 원시 값을 갖는 변수를 다른 변수에 할당하면, 원시 값 자체가 복사되어 전달된다.
  • 원시 자료형은 변경 불가능한 값(immutable value)이다. 즉, 한 번 생성된 원시 자료형은 읽기 전용(read only)이다.

참조자료형의 특징

  • 참조자료형을 변수에 할당하면, 메모리 공간에 주솟값이 저장된다.
  • 참조 값을 갖는 변수를 다른 변수에 할당하면, 주솟값이 복사되어 전달된다.
  • 참조 자료형은 변경 가능한(mutable value)이다.

저장방식에 따른 두 자료형의 차이는 메모리저장에 대한 설명에서 알 수 있다.
하지만 복사할 때의 차이점은 글로만 봐서는 이해하기가 어렵다.

먼저 원시자료형의 경우를 보자.

a = 1; // 변수 a에 1을 할당
b = a; // 변수 b에 a를 할당
a = 2; // 변수 a의 값을 변경
console.log(b); // 변수 b의 값은 1

b에 a를 할당하고, a의 값을 변경했음에도 b의 값은 그대로다. 이처럼 원시자료형은 그 값 자체를 복사했기 때문에, 복사한 이후 원래 값을 변경해도 복사한 값은 변함이 없다.

다음은 참조자료형의 경우이다.

a = [1, 2, 3]; // 변수 a에 배열을 할당
b = a; // 변수 b에 a를 할당
a[0] = 4; // 배열 a의 0번째 인덱스 값을 4로 변경
console.log(b) // 배열 b도 [4, 2, 3]으로 변경

원시자료형과 동일하게 시행하였음에도 b의 값이 변경되었다. 이는 참조자료형은 주솟값을 복사하기 때문이다. 다른 집에 저장해둔 배열의 값을 변경한들, 주솟값은 그대로이기 때문에 b도 값은 주솟값에서 변경된 배열을 받아오는 것이다.

그렇다면 변경 가능한 값(mutable value)라는 건 무슨 뜻일까?

a = 1;
a = 2;

a에 1을 할당하고, 2를 재할당하였다. 그렇다면 a라는 방의 1이 2로 바뀌는 것일까?
아니다.
새로운 a라는 방이 생성되고, 2라는 값이 저장된다. 이전에 있던 a라는 방은 이름 없는 방이 되고, 1이 그대로 남아있다. 이후 garbage collector가 쓰지 않는 1을 제거한다.
이처럼 1이 2로 변경되지 않고 새로운 공간에 저장하기 때문에 원시자료형은 변경 불가능한 값(immutalbe value)라고 하고, 참조자료형은 위의 예시에서 보았듯이 값의 변경이 가능하기 때문에 변경 가능한 값(mutable value)라고 한다.

얕은 복사와 깊은 복사

위에서 참조자료형은 주솟값을 복사하기 때문에 원래의 값을 변경하면 복사한 값도 변경된다고 하였다. 이는 반대로 복사한 값을 변경해도 원래의 값이 변경 된다는 뜻이다.

만약 복사한 배열의 요소를 수정해도 원본 배열에 영향이 가지 않도록 하려면 어떻게 해야할까?

slice(), spread syntax, Object.assign()을 사용하면 된다.

a = [1, 2, 3]; // a에 배열을 할당
b = a.slice(); // a 전체를 슬라이스한 배열을 b에 할당
a[0] = 4; // a의 요소 값 변경
console.log(b); // b = [1, 2, 3]

위의 경우, a의 요소 값을 변경했음에도 b 배열은 변함이 없다. b 배열은 a를 slice해서 만들어낸 새로운 배열이기 때문이다. 따라서 a와 b는 전혀 다른 주솟값을 가지고, 두 배열의 값도 각각의 주솟값을 가지는 집에 저장된다.

a = [1, 2, [3, 4,]]

하지만 위와 같이 참조자료형 내부에 다른 참조자료형이 중첩되어 있을 경우, slice를 통해 복사를 하면, 내부에 있는 [3, 4] 라는 참조자료형의 주솟값은 원래의 주솟값을 공유하게 된다.

이처럼 slice(), spread syntax, Object.assign() 등의 방법으로 참조자료형을 복사하게 되면, 중첩된 구조 중 한 단계만 복사하게 되는데, 이를 얕은 복사라고 한다.

반대로 중첩된 구조 전부를 복사하는 것을 깊은 복사라고 하는데, 깊은 복사를 하고 싶을 때는 JSON.stringify() 와 JSON.parse()를 함께 사용하면 된다.

JSON.stringify() > 참조자료형을 문자열 형태로 반환
JSON.parse() > 문자열의 형태를 객체로 반환

하지만 위의 방법은 함수가 포함되어 있을 경우, null을 반환하기 때문에 완전한 깊은 복사라고는 할 수 없다.

함수가 포함된 중첩된 구조의 참조자료형을 깊은 복사하기 위해선, 외부 라이브러리를 이용하는 수 밖에 없다.

node.js 환경에서 lodash나 ramda를 설치하여 내장되어있는 메서드를 사용하면 완전한 깊은 복사를 할 수 있다.
아래는 lodash를 사용하여 완전한 깊은 복사를 하는 예시이다.

const lodash = require('lodash');

const arr = [1, 2, [3, 4]];
const copiedArr = lodash.cloneDeep(arr);

console.log(arr); // [1, 2, [3, 4]]
console.log(copiedArr); // [1, 2, [3, 4]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false

lodash의 cloneDeep을 사용한 후 arr와 copiedArr의 비교 결과, 두 배열 및 중첩된 배열 또한 다른 주솟값을 사용한다는 걸 볼 수 있다.

완전한 깊은 복사를 사용하는 경우는 흔치 않기 때문에, 이러한 방법이 있다는 정도만 알면 된다.

profile
코린이

0개의 댓글