자바스크립트 데이터 타입과 메모리 영역

이보경·2024년 4월 17일
2

면접 연습을 하면서 '자바스크립트 데이터 타입에 대해 설명해주세요'에 대한 답변으로 메모리 관점에서 원시형과 참조형에 대해 설명했다. '원시형은 정말 값이 저장되나요?'라는 꼬리 질문이 들어왔고, 힙 영역에 대해 구체적으로 설명하지 못한 부분이 있어 정리하고자 한다.


자바스크립트 메모리에는 콜 스택과 메모리 힙 영역이 있다.

스택 영역은 컴파일 시 데이터의 크기를 알고 있는 정적 데이터(원시 타입)를 저장하는 영역이다. 이 콜 스택에 실행 컨텍스트가 쌓이며, 함수 실행이 완료되면 해당 실행 컨텍스트는 콜 스택에서 사라지게 된다.

  • 실행 컨텍스트를 통해 변수 식별자 저장, 스코프 체인 및 this 관리, 코드 실행 순서 관리 등을 수행한다.

힙 영역은 참조 타입 데이터가 저장되는 영역이다. 객체가 생성될 때 메모리 공간이 동적으로 할당된다.


우선 원시 타입이 메모리에 할당되는 방법을 알아보자.
let a = 'abc';

변수 영역과 데이터 영역으로 구분하여 생각하면,
1. 변수 영역 중 빈 공간(@1003)을 확보 후 식별자(a)를 저장한다.
2. 데이터 영역 중 빈 공간(@5004)에 문자열 'abc' 값을 저장한다.
3. 변수 영역에서 a 라는 식별자를 찾아, (2)의 주소를 @1003에 대입한다.


(파란색 변수a처럼 콜 스택에 저장된다는 점을 참고하기 위한 이미지)

이 식별자 a는 '실행 컨텍스트(Execution Context)의 렉시컬 환경(Lexical Environment)'이라는 곳에 저장되며 즉, 스택에서 일어나는 일이다.

이처럼 변수 영역에 데이터를 직접 대입하지 않고, 굳이 데이터 영역에 저장하고, 주소값으로 접근하는 단계를 거치는 이유는, '데이터 변환을 자유롭게' 할 수 있게 함과 동시에 '메모리를 효율적으로 관리'하기 위함이다.

  • 'abc' 값을 'abcd'라는 값으로 변경할 경우, 새로운 데이터('abcd')를 별도의 공간에 저장하고 그 주소를 연결한다.
  • 'abc' 값을 가지는 또 다른 변수가 있다면, 같은 곳을 가리키게 하여 중복된 데이터에 대한 메모리 공간을 줄일 수 있다.

변수와 상수를 구분(const, let, var) 짓는 영역은 위에서 언급한 변수 영역이며,
불변성의 여부를 구분(원시 타입, 참조 타입) 짓는 영역은 데이터 영역이다.

기본형 데이터(숫자, 문자열, boolean, null, undefined)는 모두 불변 값이다. 이는 메모리 데이터 영역에 저장된 값 자체는 변경되지 않는다는 의미이다. (새로운 공간에 값을 할당하고 변수 영역의 참조값을 변경하는 동작을 통해서 '변수의 값'을 변경하는 것이다) 즉, 한 번 만들어진 값은 가비지 컬렉팅을 당하지 않는 이상 영원히 변하지 않는다.

식별자 a의 데이터에 ‘abcdef’를 재할당하는 경우 기존에 ‘abc’가 저장되었던 공간에 ‘abcdef’를 할당하는 것이 아니라, ‘abcdef’라는 문자열을 새로 만들어 별도의 공간에 저장하고, 새로 저장된 공간의 주소를 변수 공간에 연결한다. 기존 문자열에 어떤 변환을 가하든(문자를 추가 및 제거) 상관 없이 무조건 ‘새로’ 만들어서 별도의 공간에 저장한다.


다음으로 참조 타입이 메모리에 할당되는 방법을 알아보자.
const obj = { a: 1, b: 'bbb' } ;

  1. 변수 영역 빈공간(@1002)을 확보한 후, 그 주소의 이름을 obj라고 지정한다.
  2. 임의의 데이터 저장 공간(@5001)에 데이터를 저장하려고 보니 여러 개의 프로퍼티로 이뤄진 데이터 그룹이다.
    이 그룹 내부의 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103 ~ ?) 를 @5001에 저장한다.
    (이때 객체의 프로퍼티들을 저장하기 위한 메모리 영역은 크기가 정해져 있지 않고, 필요한 시점에 동적으로 확보한다)
  3. @7103 ~ @7104 에 각각 a와 b라는 프로퍼티 이름을 저장한다.
  4. 데이터 영역에서 숫자 1을 검색한 후, 검색 결과가 없으면 임의로 @5003에 저장하고, 이 주소를 @7103에 저장한다. 문자열 'bbb' 역시 임의로 @5004에 저장하고, 이 주소를 @7104에 저장한다.

(위에 첨부한 동일한 이미지에서, 분홍색 변수b,c,d처럼 메모리 힙에 별도의 변수 영역을 확보한다는 것을 참고하자)

원시 타입과 차이점이 바로 이 객체의 변수 영역이 별도로 존재한다는 점이다.
객체가 별도로 할애한 영역은 변수 영역일 뿐, 데이터 영역은 기존의 메모리 공간(스택)을 그대로 활용하고 있다.
데이터 영역에 저장된 값은 모두 불변 값인데, 변수는 다른 값을 얼마든지 대입 할 수 있다는 특징을 가지고 있다.
그래서 흔히 참조형 데이터가 가변값이라고 하는 것이다.

예를 들어 위에서 선언했던 obj 프로퍼티 a에 대해 10으로 변경을 했다면,

  1. 데이터 영역에서 10을 찾아보고, 없다면 새로 저장한다.(@5007)
  2. @7103(obj.a)에서 가지고 있던 주소값 @5003을 @5007로 다시 저장한다.

여기서 주목할 점은 @1002에 있는 주소값은 바뀌지 않았다는 것이다. 즉, 새로운 객체가 만들어지지 않고, 객체 내부 값만 변경되었다.


주의할 점은 무조건 참조형 타입이 가변값이라고 할 수 없다는 점이다. 새로 만드는 동작을 통해서만 변경이 이루어지는 것이 불변값의 성질이다.

따라서 obj2 = obj1이 아니라 obj2 = { c: 20, d: 'ddd' }처럼 객체 자체를 할당함으로써 값을 직접 변경할 때는
메모리의 데이터 영역의 새로운 공간(@5002)에 새 객체의 주소(@7106 ~ ?)가 저장되고, 그 주소(@5002)를 변수 obj2 위치(@1003)에 저장한다.

이처럼 참조형 데이터가 가변값이라고 설명할 때의 "가변"은 데이터 내부의 프로퍼티를 변경할 때만 성립하는 것이다.


참고

0개의 댓글