[JavaScript] JS의 데이터 타입 2가지 (Primitive vs Reference)

coderH·2022년 1월 30일
1

JavaScript 연대기

목록 보기
2/11
post-thumbnail

JS의 데이터 타입

오늘은 JS의 데이터 타입 2가지에 대해서 다뤄보겠습니다.

JS에서 변수에 데이터를 저장하는 방식은 데이터의 종류에 따라
Primitive type과 Reference type 2가지로 나눠지며
JS엔진은 변수의 값에 접근할 때 해당 변수가 어떤 데이터 타입을 가지고 있는지를 먼저 판단합니다.

Primitive type은 undefined, null, boolean, number, string, symbol 총 6개의 데이터들이 포함되며
Reference type은 Object, Array, Function 등이 포함됩니다.
( JS에서는 배열과 함수도 객체에 포함되니 사실상 1개라고 볼 수 있습니다. )

그럼 왜 굳이 이렇게 2가지로 나눠놓았을까요?

이유는 데이터의 특성때문에 그렇습니다.

primitive type에 포함되는 데이터들의 크기는 고정적이고 메모리를 적게 차지하는 편인데 반해
reference type에 포함되는 데이터들의 크기는 유동적이기 때문에 언제든지 메모리를 많이 차지할 가능성이 있기 때문입니다.

그럼 이제부터 각각의 특징에 대해 이야기해보겠습니다.

Primitive type (= passed by value)

Primitive type은 변수에 값을 할당할 때 각 변수마다 독립적으로 메모리를 사용하여 저장됩니다.

따라서 변수의 값을 다른 변수로 대입하더라도 서로에게 영향을 주지 않습니다.

코드로 예시를 보겠습니다.

let a = 3;
let b = a;
b = 5;
console.log(a); // 3
console.log(b); // 5

이 코드를 그림으로 표현한다면 이렇게 표현할 수 있습니다.

b변수에 a변수를 대입하여 값을 생성하고 이후 b의 값을 5로 변경했습니다.
b는 초기화 이후 a의 값과는 상관없이 독립적이기 때문에 b의 값을 변경하더라도 a의 값은 유지됩니다.

이처럼 primitive type의 값을 가진 a와 b는 각자 메모리상에서 독립적이기 때문에
한 변수가 다른 변수의 값을 복사했더라도 서로 영향이 없습니다.

이러한 형태를 primitive type 또는 값에 의한 전달(passed by value) 이라고 합니다.

Reference type (= passed by reference)

Reference type의 변수는 변수가 데이터가 아닌 참조 값을 가지게 됩니다.

여기서 참조 값이란 메모리상의 데이터 주소(위치)와 같다고 생각하시면 되는데
변수에 값이 대입됬을때 해당 변수는 값 자체를 가지고 있는것이 아니라
값이 메모리에 저장되어있는 위치를 가지게 됩니다.

이러한 타입의 값을 가진 변수를 아래의 코드와 같이 다른 변수에 값으로써 대입한다면 이 2개의 변수들은 동일한 참조 값을 가지게 되고 한쪽에서 값을 수정한다면 같은 참조 값을 가지고 있는
다른 한 쪽도 영향을 받습니다.

따라서 a에서 데이터를 바꾸면 b에도 바뀐 데이터가 나오게 됩니다.

let a = [1, 2];
let b = a;
b.push(3);
console.log(a); // 1,2,3
console.log(b); // 1,2,3

primitive type의 예시와 같은 방식으로 변수 a의 값을 b에 복사 한 후 push를 통해 b에만 요소를 추가했지만 이렇게 a와 b의 값이 모두 변한것을 알 수 있습니다.

데이터 변경시의 동작은 아래 그림과 같습니다.

a에는 [1, 2]라는 값으로 초기화를 했고 해당 데이터의 참조 값은 a1b2c3라고 가정해보겠습니다.

b는 a의 참조 값인 a1b2c3 라는 참조 값을 복사해서 가지게 되고
이 참조 값은 실제 값인 배열 [1, 2]로 연결됩니다.

둘은 같은 참조 값을 가지고 있기 때문에 한 쪽에서 데이터를 변경한다면 a와 b 모두에게 영향이 갑니다.

위의 예시에서 데이터를 변경하는 모습은 아래의 그림과 같습니다.

그렇다면 만약 같은 값을 가진 배열을 각 변수에 아래와 같이 따로따로 선언하면 어떻게 될까요?

let a = [1, 2];
let b = [1, 2];

같은 값이지만 그림과 같이 서로 다른 참조 값을 가지게 됩니다.

이러한 reference type을 passed by reference(참조에 의한 전달) 라고도 합니다.

삼중등호 이용시 차이

Primitive type은 변수가 값을 가지고 있기 때문에 두 변수를 비교할 때 값을 기준으로 같은지 판단하게 됩니다.

// Primitive type
let a = 3;
let b = 3;
console.log(a === b); // True
b = 5;
console.log(a === b); // False

반면 Reference type의 경우 변수가 참조 값을 가지고 있으므로 참조 값이 같은지를 판단하게 됩니다.
그래서 값이 같더라도 참조값이 다르다면 false를 리턴하게 됩니다.

// 변수를 각각 선언하여 두 변수의 참조 값이 다른 상황.
let a = [1, 2];
let b = [1, 2];
console.log(a === b); // false

// 두 변수의 참조 값이 같은 상황.
let c = [1, 2];
let d = c;
console.log(c === d); // true

const

추가로 const키워드를 사용했을 때의 차이점도 다뤄보겠습니다.

const키워드는 일반적으로 변수의 초기화 이후의 값을 변경하지 못하도록 합니다.

하지만 reference type에서는 다릅니다. 값이 아니라 참조 값을 변경하지 못하도록 합니다.

그래서 array나 object 요소를 const로 선언했다면 내부 요소는 변경이 가능하지만
요소 자체를 새로 만드는것은 참조 위치가 변경되기 때문에 불가능합니다.

// Primitive type
const a = 3;
a = 5; // TypeError: Assignment to constant variable.


// Reference type
const b = [1, 2];
b.push(3);
console.log(b); // [1, 2, 3]

// 재정의 할 시 참조 값이 바뀌게 되므로 에러가 발생.
b = [3, 4]; // TypeError: Assignment to constant variable.

만약 이런 Reference type의 값 변경도 허용하고 싶지 않다면
Object의 메소드중 하나인 freeze메소드를 사용해주면 됩니다.

const b = [1, 2];
b.push(3);
console.log(b); // [1, 2, 3]

Object.freeze(b);

// 값을 추가할 때
b.push(4); // Cannot add property 4, object is not extensible


// 값을 제거할 때
b.splice(0, 1); // TypeError: Cannot assign to read only property '0' of object '[object Array]'

0개의 댓글