Primitive Type
- Number
- BigInt
- String
- Boolean
- null
- undefined
- Symbol(ES6)
Reference Type
- Array
- Function
- RegExp
- Set / WeekSet (ES6)
- Map / WeakMap (ES6)
Primitive Type 변수 할당
var a;
a = 'abc';
a
라는 변수에 값을 할당할 때 메모리에서 일어나는 과정을 살펴보자.
힙과 스택에 대한 부분은 제쳐둔 채, 그냥 ‘메모리’ 라는 관점에서만 보려고 한다.
우선 a
라는 변수를 선언했으므로 메모리의 임의의 공간(ex. 주소 1003번)에 a
라는 값을 넣는다.
다음으로 a
에 'abc'
라는 값을 할당했으므로, 이제 a
에 'abc'
라는 값을 넣어줘야 한다.
이때 1003번지의 데이터에 값을 바로 넣지 않고, 'abc'
데이터를 새로운 주소(ex. 5004번지)에 넣는다.
그리고 1003번지의 데이터의 값이 'abc'
가 있는 5004번지를 가리키도록 한다.
컴퓨터에선 변수의 할당이 이와 같이 진행된다.
var a;
a = 'abc';
a = 'abcdef';
이미 할당된 변수의 값을 변경하는 경우엔 어떻게 진행될까?
a
의 값을 'abcdef'
로 변경(재할당) 해보자.
기존의 'abc'
라는 값이 있던 5004번지의 데이터를 'abcdef'
로 변경하면 되는 것 아닌가? 라고 생각할 수 있지만, 컴퓨터는 이처럼 동작하지 않는다.
'abcdef'
라는 값을 새로운 주소의 값으로 넣고, 그 주소를 a
가 있는 주소의 값으로 연결한다.
이 과정은 1003번지에서 5004번지로 가던 연결을 5005번지로 변경했다고 생각해도 무방하다.
변수의 할당과 재할당은 위와 같이 진행된다.
Reference Type의 변수 할당
var obj;
obj = {
a : 1,
b : 'bbb',
}
var obj = { ... }
처럼 사용하더라도, 위의 코드처럼 컴퓨터는 선언과 할당을 분리해서 생각한다.
먼저 var obj;
가 선언됐으므로, obj
를 담을 주소(1002번지)를 확보한다.
이제 중괄호 값을 담을 주소를 확보하는데, 해당 값은 객체이므로 원시 타입의 과정과 다르게 특별한 과정을 더 거치게 된다.
선언 시점에선 컴퓨터가 객체의 프로퍼티가 얼마나 될 지 정학히 알 수 없으므로, 우선 5002번지에 객체가 차지할 만큼의 주소 범위를 갖게 한다.
위의 이미지에선 7103번지부터 객체 프로퍼티가 차지할 것이라고 지정했다.
현재 객체가 차지하는 공간은 7103번지부터이다.
먼저 7103번지를 a
의 주소로 지정하고, 해당 프로퍼티 값을 5003번지에 넣고 주소를 연결한다.
b
도 마찬가지로 5004번지에 프로퍼티 값을 넣고 주소를 연결한다.
참조 타입은 원시 타입과 다르게 객체가 차지하는 메모리 영역을 지정하고, 해당 영역의 주소에서 프로퍼티 값의 주소로 연결하는 과정이 추가되는 것이다.
그럼 이번엔 obj
의 프로퍼티 값을 변경(재할당)해보자.
var obj;
obj = {
a : 1,
b : 'bbb',
}
obj.a = 2;
새로운 값이 들어오므로 우선은 2
값을 5005번지에 새로 할당한다.
이제 이 5005번지 주소를 기억한 채로(hold), obj
가 있는 1002번지를 찾아간다.
1002번지에 가니 obj
의 값(객체)은 5002번지에 있다고 한다.
5002번지에는 obj
객체가 차지하고 있는 영역(프로퍼티들) 주소가 있어, a
에 해당하는 프로퍼티 주소 7103번지로 찾아간다.
7103번지의 값은 그동안 5003번지(값 : 1)를 가리키고 있었는데, 기억하고 있던 5005번지의 주소로 교체한다.
원시 타입에서 데이터를 변경할 때는 그냥 5004번지로 된 a
의 값을 5005번지로 바꾸기만 하면 됐는데, 좀 더 복잡한 단계를 거쳐 값이 변경되는 것을 볼 수 있다.
그러나, obj
가 가리키는 5002번지라는 주소는 변하지 않고 있다. 이것이 원시 타입과 참조형 타입의 메모리 할당 과정의 차이점이다.
왜 값을 직접 저장하지 않고, 주소를 저장하는걸까?
메모리의 효율성
'대충 세어봐도 30바이트가 넘는 문자열'
이라는 값을 메모리 상에 저장한다고 가정해보자.
값을 직접 저장하는 경우, 같은 값을 여러개 갖게 된다면 7000번지, 7001번지, 7002번지… 이런식의 주소에 '대충 세어봐도 30바이트가 넘는 문자열'
가 모두 들어가게 된다.
이렇게 되면 메모리 상에서 차지하는 공간은 ['대충 세어봐도 30바이트가 넘는 문자열'
* 갯수] 가 되는 것이다.
그러나, 1001번지에 '대충 세어봐도 30바이트가 넘는 문자열'
를 저장해두고, 7000번지, 7001번지, 7002번지 등에서 1001번지의 주소만 값으로 가지고 있다면, 메모리 상에서 필요로 하는 공간은 훨씬 줄어들게 된다.
두 방식에 대해서 비교해보면 아래와 같다.
값을 직접 저장 | 값의 주소를 저장 | |
데이터 할당 | 빠름 | 느림 |
비교 비용 | 비용 많이 듦 | 비용 들지 않음 |
메모리 효율 | 낮음(낭비 심함) | 높음(낭비 최소화) |
메모리 공간 | 값 크기 * 갯수 | 값 크기 + 갯수 |
유일값을 보장
값의 주소를 저장하는 방식은 비교 비용이 들지 않는다고 위에서 언급했다.
이 의미를 다시 되새겨 보면, 메모리 상에서 유일한 값을 보장하게 되고, 이는 유일값이 동시에 불변값이 되는 것이다.
데이터의 복사
원시 타입 데이터 복사
var a = 10;
var b = a;
원시 타입의 데이터를 복사하는 경우, 값이 위치한 주소 번지를 그대로 새로운 변수에 할당해주기만 하면 된다.
참조 타입 데이터 복사
var obj1 = { c : 10, d : 'ddd' };
var obj2 = obj1;
참조 타입 데이터의 복사는 각 프로퍼티의 주소들을 담고 있는 주소(5003번지)를 새로운 변수에 할당해주면 된다.
복사한 값 변경
b = 15;
obj2.c = 20;
복사한 원시 타입과 참조 타입의 값을 변경할 경우, 변수가 가지는 값(주소)가 바뀌는지 여부를 확인해보자.
b
는 새로 15
라는 값을 재할당하므로 5004번지에 15
를 넣고, b
가 가리키는 주소를 기존의 5002번지에서 5004번지로 변경한다.
그러나, obj2
는 obj2.c
의 값을 재할당하므로 5005번지에 20
의 값을 넣고, obj2.c
가 가리키는 주소를 5005번지로 참조해주기만 하면, obj2
가 가리키는 5003이라는 주소는 변하지 않는다.
참조 타입은 위와 같이 동작하기 때문에, 원본 객체의 프로퍼티 값을 바꾸거나, 복사한 객체의 프로퍼티 값을 바꿀 경우에 다른 객체까지 영향이 있는 것이다.
반면, 원시 타입은 변수가 가지는 값(주소)가 직접 변경되므로 복사한 값과 원시 값 사이에 영향이 없다.
참조가 없는 주소는 어떻게 될까?
위에서는 ‘연결’이라 표현했지만, 컴퓨터 메모리상에서 이를 참조라고 부른다.
그리고 참조하고 있는 대상이 몇 개인지 나타내는 수를 ‘참조 카운트’ 라고 한다.
이때 참조 카운트가 0인, 즉 메모리상에서 참조되지 않고 있는 주소의 값은 가비지 컬렉터(Garbage Collector)의 수집 대상이 되어 메모리 상에서 사라지게 된다.
수집 대상이 된 주소에 다른 값이 있는 주소가 저장되어 있다면, 해당 주소의 값들도 같이 메모리 상에서 사라지게 된다.
[이미지 출처 : 인프런 - 코어 자바스크립트(정재남)]
'Study > JavaScript' 카테고리의 다른 글
[JavaScript] 행렬의 곱셈 (프로그래머스 Lv2 자바스크립트) (0) | 2025.01.31 |
---|