자바스크립트에는 원시 타입과 참조 타입 2가지 자료형이 있다.
타입을 알아보기 전에 메모리 구조에 대해 짧게 얘기하고 넘어가야 이해가 더 쉽다.
JS 메모리 주소 공간
프로그램이 실행될 때 자바스크립트 엔진(V8)은 세 개의 메모리 공간을 가진다.
Code Area : 실행할 JS 코드를 저장한다.
Call Stack : 실행 중인 함수를 찾아 계산을 수행하고, 지역 변수를 저장한다. 변수들은 LIFO형식으로 저장되며, 원시 타입들이 이 곳에 저장된다.
Heap : 참조 타입들이 이 곳에 할당된다. Heap의 메모리 할당은 LIFO 형식에 따르지 않고 랜덤하게 들어간다. 메모리 누수 방지를 위해 JS 엔진의 메모리 관리자가 항상 관리한다.
📌 LIFO란?
Last In First Out의 줄임말로 가장 최근에 추가된(push) 자료가 가장 먼저 제거될(pop)될 항목이다. 콜스택은 한쪽 끝에서만 넣고 뺄 수 있는 LIFO형식의 자료구조이다.
참조 : https://velog.io/@code-bebop/JS-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0
고정된 저장 공간을 차지하는 데이터를 모두 원시 타입(primitive type)이라 한다. 원시 자료형은 모두 "하나"의 정보, 즉, 데이터를 담고 있다.
: string, number, bigint, boolean, undefined, symbol, (null)
앞서 말했듯이 변수에는 데이터의 크기와는 관계 없이 하나의 데이터만 담긴다.
const num1 = 123;
const num2 = 123456789;
원시 자료형은 값 자체에 대한 변경이 불가능(immutable)하지만, 변수에 다른 데이터를 할당할 수는 있다.
let word = "hello world!"
word = "hello codestates!"
// word라는 변수에 재할당을 하여 변수에 담긴 내용을 변경 가능
const num1 = 123;
num1 = 123456789; // 에러 발생
// const 키워드로 선언했기 때문에 재할당은 불가
원시타입을 변수로 선언하게 되면 사물함에 이름표를 붙이듯이 메모리 공간에 변수명이 붙고 그 안에 할당한 값들이 들어간다. 하나의 데이터만 담기기 때문에 재할당시 재할당된 값만을 메모리 공간에 들어간다.
원시 타입 데이터는 각 변수간에 원시타입 데이터를 복사할 경우에 데이터 값이 복사되기 때문에 기존의 데이터에 영향이 가지 않는다.
let b = 1;
let a = b;
//변수 b의 값을 재할당
b = 2;
a; //1
Heap 영역에 데이터는 별도로 관리되고, 우리가 직접 다루는 변수에는 주소가 저장되기 때문에 참조 타입(reference type) 이라 한다.
: 배열, 객체, 함수등
원시 타입 데이터는 stack 영역에 저장이 되는데 이 메모리에는 하나의 데이터만 들어갈 수 있다.
그런데 만약 이게 배열이나 객체 처럼 여러개의 값이 들어가야한다면 stack에 저장할 수 있을까? 여러개 넣는다고 하더라도 우리가 그 값을 사용하고 싶을 때 어떻게 찾아 올 수 있을까?
그래서 이때는 heap 영역을 크게 만들어주고 stack영역에 heap영역의 주소값을 넣어 heap 주소에 값을 줄줄이 연결한다. 그럼 값을 찾을 때 주소를 보고 맞는 heap 영역에서 찾아 반환한다.
Heap 영역의 메모리 공간은 "동적(dynamic)"으로 크기가 변한다.
대량의 데이터가 들어오는 경우는 고정된 데이터 공간을 사용하는 것이 비효율적이기 때문에 크기가 상황에 따라서 커졌다가 작아졌다 하는 것이다.
참조 타입들이 메모리 공간에 어떻게 들어가는지 봐보자.
arr에 배열을 할당해주고, obj에 객체를 할당해줬다.
let arr = [1,2,3,'hello'];
let obj = {
name: 'Bob',
age: 20
};
실제 데이터는 heap영역에 랜덤으로 만들어진 메모리 공간에 들어가고 stack영역에는 메모리의 주소값이 담긴다.
참조타입 데이터는 주소를 복사하기 때문에 주소 안에 데이터가 변경이 되는거라 기존의 데이터에도 영향이 간다.
참조 타입을 갖는 a 변수를 b 변수가 할당받는다면 b의 어떤 하나의 값이 바뀐다면 a변수의 값도 바뀌게 된다.
이유는 b와 a는 같은 주소값을 가지기 때문이다.
우리가 눈으로 보기에는 a변수의 배열을 b에 할당해주는 것 처럼 보이지만 실제로는 heap 영역에 주소를 변수에 할당하는 것이다.
let a = [1,2,3,'hello'];
let b = a;
b[0] = 'change';
b; // ['change',2,3,'hello'];
a; //['change',2,3,'hello'];