본문 바로가기
책 정리

코어 자바스크립트 1장 데이터 타입(04)

by 싼쵸 2022. 6. 12.
반응형
이 글은 전적으로 코어 자바스크립트를 읽고 나름대로 요약한 글이다.

출처  https://velog.io/@sisofiy626/JS-%EB%B3%80%EC%88%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-%EC%A2%85%EB%A5%98-2%EA%B0%80%EC%A7%80%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-Primitive-vs-Reference

 

04 기본형 데이터와 참조형 데이터

불변 값

변수(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'는 완전히 별개의 데이터입니다.

 

변수 b에 숫자 5를 할당합니다. 그러면 컴퓨터는 일단 데이터 영역에서 5를 찾고, 없으면 그제야 데이터 공간을 하나 만들어 저장합니다.

그 주소를 b에 저장합니다. 다음 변수 c에 같은 수인 5를 할당하려고 합니다. 컴퓨터는 데이터 영역에서 5를 찾습니다. 4번째 줄에서 이미 만들어 놓은 값이 있으니 그 주소를 재활용합니다.

 

다음 줄에서 변수 b의 값을 7로 바꾸려고 합니다. 그러면 기존에 저장된 5 자체를 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
   

1. 컴퓨터는 우선 변수 영역의 빈 공간(@1002)을 확보하고, 그 주소의 이름을 obj1로 지정합니다.

2. 임의의 데이터 저장공간(@5001)에 데이터를 저장하려고 보니, 여러 개의 프로퍼티로 이뤄진 데이터 그룹입니다. 

    그룹 내부의 프로퍼티들은 저장하기 위해 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103 ~?)를 @5001에 저장하려고 합니다.

3.  @7103 및 @7104에 각각 a와 b라는 프로퍼티 이름을 지정합니다.

4. 데이터 영역에서 숫자 1을 검색합니다. 검색 결과가 없으므로 임의로 @5003에 저장하고, 이 주소를 @7103에 저장합니다. 문자열 'bbb' 역시 임의로 @5004에 저장하고,  이 주소를 @7104에 저장합니다.

 

기본형 데이터와의 차이는 '객체의 변수(프로퍼티) 영역'이 별도 존재한다는 점입니다.

그림을 자세히 보면 객체가 별도로 할애한 영역은 변수 영역일 뿐 '데이터 영역'은 기존의 메모리 공간을 그대로 활용하고 있습니다.

데이터 영역에 저장된 값은 모두 불변 값입니다. 그러나 변수에는 다른 값을 얼마든지 대입할 수 있습니다.

그러나 변수에는 다른 값을 얼마든지 대입할 수 있습니다. 바로 이 부분 때문에 흔히 참조형 데이터는 불변하지 않다.(가변 값이다.)라고 하는 것입니다. 

 

참조형 데이터의 프로퍼티 재할당

var obj1 = {
	a : 1,
    b: 'bbb'
};

obj1.a = 2;

obj1의 a 프로퍼티에 숫자 2를 할당하려고 합니다. 데이터 영역에서 숫자 2를 검색합니다. 검색 결과가 없으므로 빈 공간인 @5005에 저장하고, 이 주소를 @7103에 저장합니다.  4번째 줄의 명령 전과 후에 변수 obj1이 바라보고 있는 주소는 @5001로 변하지 않았습니다. 즉 '새로운 객체'가 만들어진 것이 아니라 기존의 객체 내부의 값만 바뀐 것이죠

 

변수 영역

주소 1001 1002 1003 1004
데이터   이름 : obj1
값: @5001
   

데이터 영역

주소 5001 5002 5003 5004 5005
데이터 @7103 ~ ?    1 'bbb' 2

객체 @5001의 변수 영역

주소 7103 7104 7105 7106
데이터 이름 : a
값 : @5005
이름 : b
값 : @5004
   

중첩된 참조형 데이터(객체)의 프로퍼티 할당

var 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에 저장하고, 이 주소를 @8106에 저장합니다.

 

변수 영역

주소 1001 1002 1003 1004
데이터   이름 : obj1
값: @5001
   

데이터 영역

주소 5001 5002 5003 5004 5005
데이터 @7103  ~ ?  3 @8104 ~ ?  5

객체 @5001의 변수 영역

주소 7103 7104
데이터 이름 : x
값 : @5002
이름 : arr
값: @5003

객체 @5003의 변수 영역

주소 8104 8105 8106
데이터 이름 : 0
값 : @5002
이름 : 1
값: @5004
이름 : 2
값 : @5005

이제 obj.arr [1]을 검색하고자 하면 메모리에서는 다음과 같은 검색 과정을 거칩니다.

1. obj 검색 1 : obj라는 식별자를 가진 주소를 찾습니다(@1002).

2. obj 검색 2 : 값이 주소이므로 그 주소로 이동합니다.(@5001).

3. obj 검색 3: 값이 주소이므로 그 주소로 이동합니다.(@7103 ~? ).

4. obj.arr 검색 1 : arr이라는 식별자를 가진 주소를 찾습니다.(@7104)

5. obj.arr 검색 2 : 값이 주소이므로 그 주소로 이동합니다.(@5003)

6. obj.arr 검색 3 : 값이 주소이므로 그 주소로 이동합니다.(@8104 ~? )

7. obj.arr [1] 검색 1 : 인덱스 1에 해당하는 주소를 찾습니다(@8105)

8. obj.arr[1] 검색 2 : 값이 주소 이므로 그 주소로 이동합니다.(@5004).

9. obj.arr[1] 검색 3 : 값이 숫자형 데이터이므로 4를 반환합니다.

 

만약 이상태에서 재할당 명령을 내리면 어떻게 될까요??

obj.arr = 'str';

@5006에 문자열 'str'를 저장하고, 그 주소를 @7104에 저장합니다. 그러면 @5003은 더 이상 자신의 주소를 참조하는 변수가 하나도 없게 됩니다. 어떤 데이터에 대해 자신의 주소를 참조하는 변수의 개수를 참조 카운트라고 합니다. @5003의 참조 카운트는 @7104에 저장돼 있던 시점까지는 1이었다가 @7104에 @5006이 저장되는 순간 0이 됩니다. 참조 카운트가 0인 메모리 주소는 가비지 컬렉터(GC)의 수거 대상이 됩니다. 가비지 컬렉터는 런타임 환경에 따라 특정 시점이나 메모리 사용량이 포화 상태에 임박할 때마다 자동으로 수거합니다. 수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈 공간이 됩니다.

 

즉, @5003은 참조 카운트가 0이 됨에 따라 GC대상이 되고, 이후 언젠가 담겨 있던 데이터인 '@8104 ~? "라는 값이 사라집니다. 이 과정에서 연쇄적으로 @8104 ~ ? 의 각 데이터들의 참조 카운트가 0이 되고, 이들 역시 GC 대상이 되어 함께 사라 질 것입니다.

 

변수 영역

주소 1001 1002 1003 1004
데이터   이름 : obj1
값: @5001
   

데이터 영역

주소 5001 5002 5003 5004 5005
데이터 @7103  ~ ?  3 @8104 ~ ?  5

객체 @5001의 변수 영역

주소 7103 7104
데이터 이름 : x
값 : @5002
이름 : arr
값: @5006

객체 @5003의 변수 영역

주소 8104 8105 8106
데이터 이름 : 0
값 : @5002
이름 : 1
값: @5004
이름 : 2
값 : @5005

변수 복사 비교

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'  

객체 @5002의 변수 영역

주소 7103 7104  
데이터 이름 : c
값 : @5001
이름 : d
값 : @5003
 

기본형 데이터부터 살펴봅시다. 변수 영역의 빈 공간 @1001을 확보하고 식별자를 a로 지정합니다. 숫자 10을 데이터 영역에서 검색하고 없으므로 빈 공간 @5001에 저장한 다음, 이 주소를 @1001에 넣었습니다. 이로써 기본형 데이터에 대한 변수 선언 및 할당이 종료됩니다.

 

이제 복사를 할 차례입니다. 변수 영역의 빈 공간 @1002을 확보하고 식별자를 b로 지정합니다. 이제 식별자 a를 검색해  그 값을 찾아와야 합니다. @1001에 저장된 값인 @5001을 들고 좀 전에 확보해둔 @1002에 값으로 대입합니다.

 

다음으로 참조형 데이터를 볼까요? 변수 영역의 빈 공간 @1003를 확보해 식별자를 obj1로 지정합니다. 데이터 영역의 빈 공간 @5002을 확보하고, 데이터 그룹에 담겨야 하기 때문에 별도의 변수 영역 @7103 ~을 확보해 그 주소를 저장합니다.

@7103에는 식별자 c를, @7104에는 식별자 d를 입력한 다음, c에 대입할 값 10을 데이터 영역에서 검색합니다. @5001에 이미 저장돼 있으므로 이 주소를 @7103에 연결하고, 문자열'ddd'는 데이터 영역의 빈 공간에 새로 만들어서 @7104에 연결합니다. 여기까지가 참조형 데이터인 객체에 대한 변수 선언 및 할당 과정입니다.

 

5번째 줄에서는 변수 영역의 빈 공간 @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;
obj2.c = 20;

변수 영역

주소 1001 1002 1003 1004
데이터 이름 : a
값 : @5001
이름 : b
값: @5004
이름 : obj1
값: @5002
이름 : obj2
값: @5002

데이터 영역

주소 5001 5002 5003 5004 5005
데이터 10 @7103 ~ ? 'ddd' 15 20

객체 @5002의 변수 영역

주소 7103 7104  
데이터 이름 : c
값 : @5005
이름 : d
값 : @5003
 

데이터 영역에 아직 15가 없으므로 새로운 공간 @5004에 저장하고, 그 주소를 든 채로 변수 영역에서 식별자가 b인 주소를 찾습니다.

@1002의 값이 @5004가 됩니다.

데이터 영역에 20이 없으므로 새로운 공간 @5005에 저장하고, 그 주소를 든채로 변수 영역에서 obj2를 찾고(@1004), obj2값인 @5002가 가리키는 변수 영역에서 다시 c를 찾아 그곳에 5005를 대입합니다.

 

기본형 데이터를 복사한 변수 b의 값을 바꿨더니 @1002의 값이 달라진 반면, 참조형 데이터를 복사한 변수 obj2의 프로퍼티 @1004의 값은 달라지지 않았습니다. 즉, 변수 a와 b는 서로 다른 주소를 바라보게 됐으나, 변수 obj1과 변수 obj2는 여전히 같은 객체를 바라보고 있는 상태입니다. 이를 코드로 표현하면 다음과 같습니다.

a !== b
obj1 === obj2

이 결과가 바로 기본형과 참조형 데이터의 가장 큰 차이점입니다. 

대부분의 자바스크립트 책에서는 '기본형은 값을 복사하고 참조형은 주 솟값을 복사한다'고 설명하고 있지만, 사실은 어떤 데이터 타입이든 변수에 할당하기 위해서는 주솟값을 복사해야 하기 때문에, 엄밀히 따지면 자바스크립트의 모든 데이터 타입은 참조형 데이터일 수밖에 없습니다.

 

일반적으로 '기본형도 결국 주 솟값을 참조한다'는 사실을 소개하지 않는데, 그 이유는 이해하기에 다소 어려움이 있기 때문이라고 추측됩니다. 그러나 이런 내부 원리를 잘 이해하는 것은 향후 중급 개발자로 성장하는 과정에서 더 큰 혼란을 느끼지 않기 위한 중요한 초석이 될 것이라고 믿습니다.

 

한 가지 더 짚고 넘어갈 내용이 있습니다. 변수의 값(b)을 직접 변경할 때와 값이 아닌 내부 프로퍼티(obj2.c)를 변경할 때의 결과를 비교한 것입니다. 가만 보면 애초에 비교 대상의 조건 자체가 서로 다르니 동작도 다른 게 당연할 수밖에 없습니다.

var a = 10;
var b = a;
var obj1 = { c : 10, d: 'ddd'};
var obj2 = obj1;

b = 15;
obj2 = { c: 20, d: 'ddd'}

이번에는 b의 경우와 마찬가지로 obj2에도 새로운 객체를 할당함으로써 값을 직접 변경했습니다. 그러면 메모리의 데이터 영역의 새로운 공간에 새 객체가 저장되고, 그 주소를 변수 영역의 obj2위치에 저장하겠죠. 객체에 대한 변경임에도 값이 달라졌습니다!!!

주소  1002 1002 1003 1004 1005 1006
데이터 이름 : 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
값 : @5004
주소 8204 8205
데이터 이름 : c
값 : @5005
이름 : d
값 : @5003

즉, 참조형 데이터가 '가변 값'이라고 설명할 때의 '가변'은 참조형 데이터 자체를 변경할 경우가 아니라 그 내부의 프로퍼티를 변경할 때만 성립합니다.

 

04의 경우 내용도 길고, 조금 이해하기 어려운 부분이 많아서 따로 작성을 했다.
확실히 그냥 읽을 때 보다 글로 옮기니 조금 더 기억에 남고 이해도가 올라가는 게 느껴진다.

반응형

댓글