불변값과 가변값을 나누는 기준은 뭘까?
0. 미리보는 결론
불변값과 가변값은 데이터 영역의 메모리를 변경할 수 있는가에 따라 나뉜다.
기본형 데이터 타입의 값을 변경할 때는 데이터 영역의 메모리를 변경하지 않고(불변성) 변수의 값만 재할당하는 반면,
참조형 데이터 타입의 값은 묶음을 가리키는 주솟값을 가지며 이 주소에 있는 값은 데이터 영역에 위치하여 객체 내부 변수들의 주솟값을 참조하고 있기 때문에, 프로퍼티 변경 시 데이터 영역의 메모리를 변경하여 값을 변경하는 것처럼 보인다.(가변성)
1. 변수에 값을 할당하는 과정
- 데이터 영역 메모리의 값을 직접 바꾸지 않는다.
- 값을 재할당함으로써 데이터를 변경한다.
- 바꾸고자 하는 값을 검색한다.
2-1) 값이 존재하면, 그 데이터의 주솟값을 할당한다
2-2) 값이 존재하지 않다면, 새로 데이터를 만든다. - 재할당 과정에서 참조되고있지 않은 메모리는 GC가 별도로 컬렉팅해간다.
2. 기본형 데이터 타입
var a = 'abc';
var b = 'aaa';
a = 'aaa';

변수 a의 데이터는 'abc'를 'aaa'값으로 바꾼 것이 아니라, 'aaa'로 재할당한 것이다. @5003에 있는 데이터 자체를 변경하지 않고, @5004를 재할당하며, 후에 @5003은 참조하는 변수가 없기 때문에 GC가 수거했다.
이처럼 데이터 영역의 메모리를 직접 변경하지 않기 때문에 기본형 데이터 타입은 불변성을 띈다고 할 수 있다.
4
3. 참조형 데이터 타입
참조형 데이터 타입은 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주솟값을 갖고 있다.
var obj1: { a : 1 , b : 'aaa' };
obj1.a = 'aaa';

obj1은 obj1.a라는 프로퍼티 값이 변경됐음에도, obj1 주소값은 @5002로 예전과 동일하다.
객체 내부의 데이터를 재할당한다고 해서 새로운 객체를 데이터 영역에 할당하지 않는다. 주솟값은 그대로 유지한채 객체 내부의 데이터의 주솟값을 갱신한다. 그렇게되면 obj1의 데이터는 @5002로 동일하지만 그 안의 데이터는 새롭게 변경된 것 처럼 보인다. 데이터 영역의 메모리를 직접 변경하지 않은 기본형 데이터 타입과 다르게, 참조형 데이터 타입은 데이터 영역의 메모리(@5002)의 값인 @7001~의 데이터를 변경한다는 점 때문에 가변성을 띈다라고 할 수 있다.
4. 정리
불변성과 가변성을 논하기 위해선, Primitive type, Non-primitive type으로 데이터 타입을 구분하는 것이 의미가 명확해진다. 모든 JS의 변수는 Primitive type에 변수 할당 과정을 따른다. 하지만 데이터의 묶음을 표현하기 위해 Non-primitive type이 등장하면서 얘기가 달라진다. Non-primitive type으로 변수를 만들면, 묶음을 가리키는 주솟값을 가지게 되는데, 이 데이터 내부의 값은 객체 내부의 또다른 변수 영역이다. 그렇기 때문에 Non-primitive type의 값을 변경하지 않아도 내부 변수의 값을 자유롭게 변경할 수 있다. 따라서 내부 변수를 바꿀 경우, Non-primitive type의 주솟값은 동일하지만, 실제 데이터는 새롭게 변경된 것처럼 보인다. 변수(Non-primitive type)의 값은 같아도 그 안의 데이터(객체 내부의 또다른 변수 영역)는 달라진 것처럼 보이기 때문에, 이 성질을 가변성이라고 부른다.
5. 추가
변수의 값을 변경했을 때 변경 전의 데이터 메모리를 바꾸는지에 따라 불변값과 가변값이 나뉜다.
기본형 타입의 변수는 값을 변경했을 때, 변수의 할당 값만 바뀔뿐 실제 데이터가 들어가 있는 메모리에는 변화가 없지만, 참조형 타입의 변수는, 프로퍼티를 변경했을 때를 한정해, 데이터 메모리에 변화가 생긴다. 이는 참조형 타입 변수는 내부 변수들의 주솟값들의 묶음을 가리키는 데이터 영역 메모리의 주솟값을 갖고 있기 때문이다. 이 데이터 영역의 메모리는 참조형 타입의 내부 변수들의 주소값들이 들어가 있기 때문에, 내부 변수의 값이 바뀔 때 마다 데이터 영역의 메모리는 바뀌어 가변성이 있다라고 한다.
6. 가변성이란 원본의 훼손 가능성을 뜻하기도 한다.
var a = 1
var b = a
a = 9999
console.log(b) // 100
- a는 1이라는 데이터 영역 메모리 주솟값을 바로 가리키고 있기 때문에 b는 1의 주솟값을 복사하게 되어, 그 후에 a값이 변하더라도 b의 값은 변하지 않는다. b라는 원본이 a라는 다른 변수에 값을 재할당하더라도 값이 변하지 않기 때문에, 훼손되지 않기 때문에, 불변값이 되는 것이다.
var obj1 = {a:1}
var obj2 = obj1
obj1.a = 9999
console.log(obj2.a) // 9999
- obj1은 데이터 묶음을 가리키고 있는 주솟값을 가리키고 있고, 이를 obj2가 복사했기 때문에 obj1의 프로퍼티가 변함에 따라 obj2의 프로퍼티도 변한다. obj2라는 원본이 obj1이라는 변수에 내부 프로퍼티를 재할당하면 값이 변하기 때문에, 훼손되기 때문에, 가변값이라고 한다.
var obj1 = {a:1}
var obj2 = {...obj1}
obj1.a = 9999
console.log(obj2.a) // 1
- obj2는 {}, 객체리터럴 방식으로 새로운 변수를 만들었기 때문에 obj1과 obj2는 서로 다른 데이터 묶음을 가리킨다. 그 후 obj2는 Spread Operator방식으로 obj1의 내부 프로퍼티를 복사했다. obj1이 중첩 객체로 만들어지지 않는 이상, obj2의 내부 프로퍼티는 값을 데이터 영역의 메모리를 바로 복사해와, obj1의 내부 프로퍼티가 변하더라도 obj2의 값은 변하지 않기 때문에, obj2라는 원본을 훼손시키지 않아, 불변값이라고 할 수 있다.