변수와 상수를 구분하는 성질은 "변경 가능성"이다.
변수와 상수를 구분 짓는 변경 가능성의 대상은 변수 영역 메모리이다.
불변성 여부를 구분할 때의 변경 가능성의 대상은 데이터 영역 메모리이다.
기본형 데이터인 Number, String, Boolean, null, undefined, Symbol은 모두 불변값이다.
숫자와 문자열을 예로 들어 불변성의 개념을 알아보자.
let a = 'abc';
a = a + 'def';
변수 a에 문자열 'abc'를 할당했다가 새로운 문자열 'abcdef'를 만들어 그 주소를 변수 a에 저장한다. 'abc'와 'abcdef'는 완전히 별개의 데이터이다.
let b = 5;
let c = 5;
변수 b에 숫자 5를 할당한다. 컴퓨터는 데이터 영역에서 5를 찾고, 없으면 데이터 공간을 하나 만들어 저장한다. 그 주소를 b에 저장한다.
그 다음 줄에서는 컴퓨터가 데이터 영역에서 5를 찾는다. 아까 만든 그 주소를 재활용한다.
b = 7;
변수 b의 값을 7로 바꾸고자 한다. 기존에 저장했던 7을 찾아서 있으면 재활용하고, 없으면 새로 만들어서 b에 저장한다. 5 와 7 모두 다른 값으로 변경할 수 없다.
변경은 새로 만드는 동작을 통해서만 이루어진다. 이것이 불변값의 성질이다.
참조형 데이터의 기본적인 성질은 가변값인 경우가 많지만 설정에 따라 변경 불가능한 경우도 있고 아예 불변값으로 활용하는 방안도 있다.
let obj1 = {
a: 1,
b: 'bbb'
};
1. 컴퓨터는 우선 변수 영역의 빈 공간(@1002)을 확보하고, 그 주소의 이름을 obj1으로 지정한다.
2. 임의의 데이터 저장공간(@5001)에 데이터를 저장하려고 보니 여러 개의 프로퍼티로 이뤄진 데이터 그룹이다. 이 그룹 내부의 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103~?)를 @5001에 저장한다.
3. @7103 및 @7104에 각각 a와 b라는 프로퍼티 이름을 지정한다.
4. 데이터 영역에서 숫자 1을 검색한다. 검색 결과가 없으므로 임의로 @5003에 저장하고, 이 주소를 @7103에 저장한다. 문자열 'bbb'역시 임의로 @5004에 저장하고, 이 주소를 @7104에 저장한다.
객체의 변수(프로퍼티)영역이 별도로 존재한다. 객체가 별도로 할애한 영역은 변수 영역일 뿐 '데이터 영역'은 기존의 메모리 공간을 그대로 활용하고 있다. 데이터 영역에 저장된 값은 모두 불변값이다. 그러나 변수에는 다른 값을 얼마든지 대입할 수 있다.
=> 그러므로, 참조형 데이터는 불변하지 않다.
let obj1 = {
a: 1,
b: 'bbb'
}
obj1.a = 2;
obj1의 a프로퍼티에 숫자 2를 할당하려고 한다.
변수 obj1이 바라보고 있는 주소는 @5001로 변하지 않았다.
let obj = {
x: 3,
arr: [3, 4, 5]
};
1. 컴퓨터는 우전 변수 영역의 빈 공간(@1002)을 확보하고, 그 주소의 이름을 obj로 지정한다.
2. 임의의 데이터 저장공간(@5001)에 데이터를 저장하려는데, 이 데이터는 여러 개의 변수와 값들을 모아놓은 그룹(객체)이다. 이 그룹의 각 변수(프로퍼티)들을 저장하기 위해 별도의 변수 영역을 마련하고(@7103~?), 그 영역의 주소를 @5001에 저장한다.
3. @7103에 이름 x를, @7104에 이름 arr를 지정한다.
4. 데이터 영역에서 숫자 3을 검색한다. 없으므로 임의로 @5002에 저장하고, 이 주소를 @7103에 저장한다.
5. @7104에 저장할 값은 배열로서 역시 데이터 그룹이다. 이 그룹 내부의 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고(@8104~?), 그 영역의 주소 정보(@8104~?)를 @5003에 저장한 다음, @5003을 @7104에 저장한다.
6. 배열의 요소가 총 3개이므로 3개의 변수 공간을 확보하고 각각 인덱스를 부여한다(0, 1, 2).
7.데이터 영역에서 숫자 3을 검색해서(@5002) 그 주소를 @8104에 저장합니다.
8. 데이터 영역에서 숫자 4가 없으므로 @5004에 저장하고, 이 주소를 @8105에 저장한다.
9. 데이터 영역에 숫자 5가 없으므로 @5005에 저장하고, 이 주소를 @8105에 저장한다.
obj.arr[1]을 검색하고자 하면 메모리에서는 다음과 같은 검색 과정을 거친다.
@1002 -> @5001 -> (@7103 ~ ?) -> @5003 -> (@8104 ~ ?) -> @8105 -> @5004 -> 4 반환
obj.arr = 'str';
@5006에 문자열 'str'을 저장하고 그 주소를 @7104에 저장한다.
@5003의 참조 카운트가 0이 되고, 연쇄적으로 @8104 ~ ?의 각 데이터들의 참조 카운트도 0이 된다.
참조 카운트가 0인 메모리주소는 가비지컬렉터의 수거대상이 되고 런타임 환경에 따라 특정 시점이나 메모리 사용량이 포화 상태에 임박할 때마다 자동으로 수거된다.
기본형 데이터를 보자.
let a = 10;
let b = a;
참조형 데이터를 보자.
let obj1 = { c: 10, d: 'ddd' };
let obj2 = obj1;
let a = 10;
let b = a;
let obj1 = { c: 10, d: 'ddd' };
let obj2 = obj1;
b = 15;
obj2.c = 20;
변수 a, b는 서로 다른 주소를 바라보게 됐으나, 변수 obj1과 obj2는 여전히 같은 객체를 바라보고 있는 상태이다.
일반적으로는 기본형도 결국 주솟값을 참조한다. 다만 기본형은 주솟값을 복사하는 과정이 한 번만 이뤄지고, 참조형은 한 단계를 더 거치게 된다.
obj2 = { c: 20, d: 'ddd' };
obj2에도 새로운 객체를 할당함으로써 값을 직접 변경한 경우, 데이터 영역의 새로운 공간에 개 객체가 저장되고 그 주소를 변수 영역의 obj2위치에 저장한다.
즉, 참조형 데이터가 "가변값"이라고 설명할 때의 "가변"은 참조형 데이터 자체를 변경할 경우가 아닌, 그 내부의 프로퍼티를 변경할 때만 성립한다.