4. 기본형 데이터와 참조형 데이터
불변값
변수variable와 상수constant를 구분하는 성질은 변경 가능성이다.
변수는 변경이 가능하고
상수는 변경이 불가능하다.
그런데, 불변값과 상수를 같은 개념으로 오해하기 쉬운데, 이 둘은 명확히 구분할 필요가 있다.
변수와 상수를 구분짓는 변경 가능성의 대상은 변수 영역 메모리이다.
즉, 한 번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당할 수 있는지의 여부가 관건인 것이다.
그러나, 불변성 여부를 구분할 때의 변경 가능성의 대상은 데이터 영역 메모리이다.
불변성이 정확히 무엇을 뜻하는 것일까?
기본형 데이터인 숫자, 문자열, boolean, null, undefined, Symbol은 모두 불변값이다.
var a = 'abc';
a = a + 'def';
var b = 5;
var c = 5;
b = 7;
지난 시간에 변수 a에 문자열 'abc'를 할당했다가, 뒤에 'def'를 추가하면
기존의 'abc'가 'abcdef'로 바뀌는것이 아니라, 새로운 문자열 'abcdef'을 만들어 그 주소를 변수 a에 저장하는 것이라고 배웠다.
즉, 'abc'와 'abcdef'는 완전히 별개의 데이터인 것이다.
4번째 줄에서 변수 b에 숫자 5를 할당하는데,
컴퓨터는 일단 데이터 영역에서 5를 찾고, 없으면 그제서야 데이터 공간을 하나 만들어 저장한다.
그리고 그 주소를 b에 저장한다.
그 다음 줄에서 c에 같은 수인 5를 할당하려고 한다.
컴퓨터는 먼저 데이터 영역에서 5를 찾게 되는데, 이미 4번째 줄에서 만들어 놓은 겂이 있으니, 그 주소를 재활용한다.
마지막 줄에서 변수 b의 값을 7로 바꾸려고 하는데,
기존에 저장했던 7이 있으면 재활용하고, 없으면 새로 만들어 b에 저장한다.
따라서, 5와 7은 모두 다른 값으로 변경할 수 없다.
정리하면, 불변값의 성질은 다음과 같다.
문자열이고 숫자 값이고 간에 한 번 만든 값을 바꿀 수 없다. 변경은 새로 만드는 동작을 통해서만 이뤄진다.
가변값
기본형 데이터는 모두 불변값이다.
그러면 참조형 데이터는 어떨까?
(기본형과 참조형에 대해서 첫 장에서 이미 다루었다.)
참조형 데이터는 기본 성질이 가변값인 경우가 많지만, 성질에 따라 변경 불가할 수도 있다.
참조형 데이터를 변수에 할당하는 과정을 먼저 살펴보자.
var obj1 = {
a: 1,
b: 'bbb'
};
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | .... |
데이터 | 이름: obj1 값: @5001 |
|||||
데이터 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | .... |
데이터 | @7103 ~ ? | 1 | 'bbb' |
↓
객체 @5001의 변수 영역 |
주소 | 7103 | 7104 | 7105 | 7106 | .... |
데이터 | 이름: a 값: @5003 |
이름: b 값: @5004 |
- 컴퓨터는 변수 영역의 빈 공간(@1002)를 확보하고, 그 주소의 이름을 obj1로 지정한다.
- 임의의 데이터 저장 공간 @5001에 데이터를 저장하려고 보니 여러 개의 프로퍼티로 이뤄진 데이터 그룹이다. 이 그룹 내부의 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103~?)를 @5001에 저장한다.
- @7103및 @7104에 각각 a와 b라는 프로퍼티 이름을 지정한다.
- 데이터 영역에서 숫자 1을 검색한다. 검색 결과가 없으므로 임의로 @5003에 저장하고, 이 주소를 @7103에 저장한다. 문자열 'bbb'역시 임의로 @5004에 저장하고, 이 주소를 @7104에 저장한다.
기본형 데이터와의 차이는 객체의 변수(프로퍼티) 영역이 별도로 존재한다는 점이다.
그림을 보면, 객체가 별도로 할애한 영역은 변수 영역일 뿐, 데이터 영역은 기존의 메모리 공간을 그대로 활용하고 있다.
데이터 영역에 저장된 값은 모두 불변값이다.
그러나 변수에는 다른 값을 얼마든지 대입할 수 있다. 바로 이 부분 때문에 흔히 참조형 데이터는 가변값이라고 하는 것이다.
참조형 데이터의 프로퍼티 재할당 예제를 보자.
var obj1 = {
a: 1,
b: 'bbb'
};
obj1.a = 2;
obj1의 a프로퍼티에 숫자 2를 할당하고자 한다.
데이터 영역에서 숫자 2를 검색하고, 검색 결과가 없으므로 빈 공간인 @5005에 저장하고, 이 주소를 @7103에 저장한다.
변수 obj1이 바라보고 있는 주소는 @5001로 변하지 않았다. 새로운 객체가 만들어진 것이 아니라, 기존의 객체 내부의 값만 바뀐 것이다.
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | 1005 |
데이터 | 이름: obj1 값: @5001 |
|||||
데이터 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | 5005 |
데이터 | @7103 ~ ? | 1 | 'bbb' | 2 |
객체 @5001의 변수 영역 |
주소 | 7103 | 7104 | 7105 | 7106 | .... |
데이터 | 이름: a 값: @5005 |
이름: b 값: @5004 |
이번에는 참조형 데이터의 프로퍼티에 다시 참조형 데이터를 할당하는 경우를 살펴보자.
이 경우를 중첩 객체nested object라고 한다.
var obj = {
x: 3,
arr: [3,4,5]
};
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | 1005 |
데이터 | 이름: obj1 값: @5001 |
|||||
데이터 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | 5005 |
데이터 | @7103 ~ ? | 3 | @8104~? | 4 | 5 |
객체 @5001의 변수 영역 |
주소 | 7103 | 7104 | 7105 | 7106 | .... |
데이터 | 이름: x 값: @5002 |
이름: arr 값: @5003 |
객체 @5003의 변수 영역 |
주소 | 8104 | 8105 | 8106 | .... | |
데이터 | 이름: 0 값:@5002 |
이름: 1 값: @5004 |
이름: 2 값: @5005 |
- 컴퓨터는 변수 영역의 빈 공간(@1002)를 확보하고, 그 주소의 이름을 obj로 지정한다.
- 임의의 데이터 저장공간(@5001)에 데이터를 저장하려는데, 이 데이터는 여러 개의 변수와 값들을 모아놓은 그룹(객체)이다. 이 그룹의 각 변수(프로퍼티)들을 저장하기 위해 별도의 변수 영역을 마련하고(@7103~?) 그 영역의 주소를 @5001에 저장한다.
- @7103에 이름 x, @7104에 이름 arr를 지정한다.
- 데이터 영역에서 숫자 3을 검색한 뒤, 없으므로 임의로 @5002에 저장하고 이 주소를 @7103에 저장한다.
- @7104에 저장할 값은 배열로 역시 데이터 그룹임. 그룹 내부의 프로퍼티를 저장하기 위해 별도의 변수 영역을 마련하고(@8104~?), 그 영역의 주소 정보를 @5003에 저장한 다음 @5003을 @7104에 저장한다.
- 배열의 요소가 총 3개이므로 3개의 변수 공간을 확보하고 각각 인덱스를 부여한다(0,1,2)
- 데이터 영역에서 숫자 3을 검색해서(@5002) 그 주소를 @8104에 저장한다.
- 데이터 영역에 숫자 4가 없으므로 @5004에 저장하고 이 주소를 @8105에 저장한다.
- 데이터 영역에 숫자 5가 없으므로 @5005에 저장하고, 이 주소를 @8106에 저장한다.
obj.arr[1]을 검색하고자 하면 메모리는 다음과 같은 검색 과정을 거친다.
@1002 -> @5001-> (@7103~?) ->@7104 ->@5003 ->(@8104~?) ->@8105-> @5004 -> 4반환
이 상태에서 재할당 명령을 내리면,
obj.arr = 'str';
임의의 @5006에 문자열 'str'을 저장하고, 그 주소를 @7104에 저장한다.
@5003은 더 이상 자신의 주소를 참조하는 변수가 하나도 없게 된다.
어떤 데이터에 대해 자신의 주소를 참조하는 변수의 개수를 참조 카운트라고 하는데,
@50003의 참조 카운트는 @7104에 @5003이 저장되어 있던 시점까지는 1이었다가, @7104에 @5006이 저장되는 순간 0이 된다.
참조 카운트가 0인 메모리 주소는 가비지 컬렉터(CG)의 수거 대상이 된다.
가비지 컬렉터는 런타임 환경에 따라 특정 시점이나 메모리 사용량이 포화상태에 임박할 때마다 자동으로 대상들을 수거하고, 수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈 공간이 된다.
변수 복사 비교
지금까지는 동작 방식에 대해 알아보았다.
기본형 데이터와 참조형 데이터의 차이를 본격적으로 확인해보자.
변수를 복사할 때의 변화를 먼저 살펴보자.
var a = 10;
var b = a;
var obj1 = { c:10, d:'ddd' };
var obj2 = obj1;
변수 영역 | 주소 | 1001 | 1002 | 1003 | 1004 | |
데이터 | 이름: a 값: @5001 |
이름: b 값: @5001 |
이름: obj1 값: @5002 |
이름: obj2 값: @5002 |
||
데이터 영역 | 주소 | 5001 | 5002 | 5003 | 5004 | |
데이터 | 10 | @7103~? | 'ddd' |
객체 @5001의 변수 영역 |
주소 | 7103 | 7104 | 7105 | 7106 | .... |
데이터 | 이름: c 값: @5001 |
이름: d 값: @5003 |
기본형의 변수 복사시에,
변수 영역의 빈 공간 @1002를 확보하고 식별자를 b로 지정한다.
식별자 a를 검색해 그 값을 찾아온다.
@1001에 저장된 값인 @5001을 들고 좀 전에 확보해둔 @1002에 값으로 대입한다.
참조형의 변수 복사시에,
변수 영역의 빈 공간 @1004를 확보하고 식별자를 obj2로 지정한다.
식별자 obj1을 검색해(@1003) 그 값인 @5002를 들고, @1004에 값으로 대입한다.
변수를 복사하는 과정은 기본형 데이터와 참조형 데이터 모두 같은 주소를 바라보게 되는 점에서 동일하다.
@1001과 @1002는 모두 값이 @5001이 되었고,
@1003과 @1004에는 모두 값이 @5002가 되었다.
복사 과정은 동일하지만, 데이터 할당 과정에서 이미 차이가 있기 때문에,
변수 복사 이후의 동작에 큰 차이가 발생한다.
var a = 10;
var b = a;
var obj1 = { c:10, d:'ddd' };
var obj2 = obj1;
b = 15;
obj.c = 20;
주소 | 1001 | 1002 | 1003 | 1004 | 1005 | ... |
데이터 | 이름: a 값: @5001 |
이름: b 값: @5004 |
이름: obj1 값: @5002 |
이름: obj2 값: @5002 |
||
주소 | 5001 | 5002 | 5003 | 5004 | 5005 | .... |
데이터 | 10 | @7103~? | 'ddd' | 15 | 20 |
주소 | 7103 | 7104 | ... |
데이터 | 이름: c 값: @5005 |
이름: d 값: @5003 |
기본형 데이터를 복사한 변수 b의 값을 바꾸면 @1002의 값이 달라진 반면,
참조형 데이터를 복사한 변수 obj2의 프로퍼티 값을 바꾸었더니, @1004의 값은 달라지지 않았다.
즉, 변수 a와 b는 서로 다른 주소를 바라보게 되었으나,
변수 obj1과 obj2는 여전히 같은 객체를 바라보고 있는 상태이다.
이를 코드로 표현하면 다음과 같다.
a !== b
obj1 === obj2
이 결과가 바로 기본형과 참조형 데이터의 가장 큰 차이점이다.
대부분의 자바스크립트 책에서 기본형은 값을 복사하고 참조형은 주솟값을 복사한다고 설명하고 있지만,
사실은 어떤 데이터 타입이든 변수에 할당하기 위해서는 주솟값을 복사해야 하기 때문에
엄밀히 따지면 자바스크립트의 모든 데이터 타입은 참조형 데이터일 수밖에 없다.
다만, 기본형은 주솟값을 복사하는 과정이 한 번만 이뤄지고,
참조형은 한 단계를 더 거치게 된다는 차이가 있다.
한 가지 더 짚고 넘어갈 점이 있다.
직접 변수의 값(b)을 직접 변경할 때와 값이 아닌 내부 프로퍼티(obj.c)를 변경할 때의 결과를 비교한 것이다.
비교 대상이 다른 것이다.
같은 조건일 때의 동작도 확인해보자.
var a = 10;
var b = a;
var obj1 = { c:10, d:'ddd' };
var obj2 = obj1;
b = 15;
obj2 = { c:20, d:'ddd' };
obj2에도 새로운 객체를 할당함으로 값을 직접 변경했다.
이렇게 하면, 객체에 대한 변경임에도 값이 달라진 것을 확인할 수 있다.
주소 | 1001 | 1002 | 1003 | 1004 | 1005 | ... |
데이터 | 이름: a 값: @5001 |
이름: b 값: @5004 |
이름: obj1 값: @5002 |
이름: obj2 값: @5006 |
||
주소 | 5001 | 5002 | 5003 | 5004 | 5005 | 5006 |
데이터 | 10 | @7103~? | 'ddd' | 15 | 20 | @8204~? |
주소 | 7103 | 7104 | ... |
데이터 | 이름: c 값: @5001 |
이름: d 값: @5003 |
주소 | 8204 | 8205 | ... |
데이터 | 이름: c 값: @5005 |
이름: d 값: @5003 |
즉, 참조형 데이터가 가변값이라고 설명할 때의 가변은 참조형 데이터 자체를 변경할 경우가 아니라, 그 내부의 프로퍼티를 변경할 때만 성립하게 된다.
'자바스크립트 공부하기' 카테고리의 다른 글
[코어자바스크립트] 1단원 정리 (0) | 2021.05.11 |
---|---|
[코어자바스크립트] 6.undefined와 null (0) | 2021.05.11 |
[코어자바스크립트] 5.불변 객체 (0) | 2021.05.11 |
[코어자바스크립트] 3.변수 선언과 데이터 할당 (0) | 2021.05.11 |
[코어 자바스크립트] 1-1 데이터 타입의 종류 (0) | 2021.05.08 |