새소식

프론트엔드 공부/자바스크립트

원시 자료형, 참조 자료형의 특징과 종류

  • -

참조 : codestateswebclub, mdn

원시 자료형(Primitive Type), 참조 자료형(Reference Type)의 특징과 종류


자바스크립트 데이터 타입은 크게 두가지인 원시형(Primitive Type)과 참조형(Reference Type)으로 분리됩니다.

  • 기본(원시)형에는 Number, String, Boolean, null, undefined 가 있으며 ES6 에서는 Symbol 도 추가되었습니다.
  • 참조형은 대표적으로 객체(Object)가 있고 그 하위에 배열(Array), 함수(Function), 정규표현식(RegExp) 등이 있으며, ES6에서는 Map, Set, WeakMap, WeakSet 등도 추가되었습니다.

webclub 기본형과 참조형 차이점 정리


원시형(Primitive Type)

JavaScript에서 원시 값(primitive, 또는 원시 자료형)이란 객체가 아니면서 메서드도 가지지 않는 데이터입니다. 원시 값에는 7종류, string, number (en-US), bigint (en-US), boolean, undefined, symbol, 그리고 null이 존재합니다.

대부분의 경우, 원시 값은 언어 구현체의 가장 저급(low level) 단계에서 나타냅니다.

모든 원시 값은 불변하여 변형할 수 없습니다. 원시 값 자체와, 원시값을 할당한 변수를 혼동하지 않는 것이 중요합니다. 변수는 새로운 값을 다시 할당할 수 있지만, 이미 생성한 원시 값은 객체, 배열, 함수와는 달리 변형할 수 없습니다.

// 문자열 메서드는 문자열을 변형하지 않음
let bar = "baz";
console.log(bar);        // baz
bar.toUpperCase();
console.log(bar);        // baz

// 배열 메소드는 배열을 변형함
let foo = [];
console.log(foo);        // []
foo.push("plugh");
console.log(foo);        // ["plugh"]

// 할당은 원시 값에 새로운 값을 부여 (변형이 아님)
bar = bar.toUpperCase(); // BAZ

 

let data1 = 1; //data1 = 1  
let data2 = data1; //data2 = 1 //data2라는 변수에 data1의 값을 복사해 넣어주었다.  
data2 = 2; //data2 = 2

data1 //1  
data2 //2  
//data1의 값에는 아무런 영향이 가지 않는다.

JavaScript에서의 원시 래퍼 객체

null과 undefined 를 제외하고, 모든 원시 값은 원시 값을 래핑한 객체를 갖습니다.

  • 문자열 원시 값을 위한 String 객체.
  • 숫자 원시 값을 위한 Number 객체.
  • 빅인트 원시 값을 위한 BigInt 객체.
  • 불리언 원시 값을 위한 Boolean 객체.
  • 심볼 원시 값을 위한 Symbol 객체.

래퍼 객체의 valueOf() 메서드는 원시 값을 반환합니다.


참조형(Reference Type)

JavaScript에서 원시 자료형이 아닌 모든 것은 참조 자료형입니다. 배열([])과 객체({}), 함수(function(){})가 대표적입니다. 이런 자료형을 JavaScript에서는 참조 자료형(reference data type; 참조 타입)이라고도 부릅니다.

참조 자료형의 데이터 자체는 원시 자료형이 보관되는 데이터 보관함이 아닌 특별한 데이터 보관함에 저장됩니다. 이 데이터가 위치한 곳(메모리 상 주소)을 가리키는 주소가 변수에 저장됩니다. 즉, 변수에는 특별한 데이터 보관함을 찾아갈 수 있는 주소가 담겨있고, 이 주소를 따라가 보면 특별한 데이터 보관함을 찾을 수 있는데, 이 특별한 데이터 보관함에서는 자기 마음대로 사이즈를 늘렸다가 줄였다가 합니다. ("동적(dynamic)으로 변한다"라고 하기도 합니다.) 이처럼 데이터는 별도로 관리되고, 우리가 직접 다루는 변수에는 주소가 저장되기 때문에 reference data type이라고 불립니다. 이런 특별한 데이터 보관함을 heap이라고도 부릅니다.

코드스테이츠 참조자료형

어떻게 코드를 작성하느냐에 따라서 담길 수 있는 정보의 크기가 천차만별이다. 그렇기 때문에 동적(dynamic)으로 변수의 크기가 변할 수 있어야 합니다. 원시자료형의 경우 stack에 직접적인 변수의 값이 들어갑니다.

참조자료형의 경우, 메모리에 공간에 데이터를 저장하고, 변수에는 그 공간의 주소만 참조합니다. stack에는 heap의 주소가 들어가게 되고 heap에 데이터가 들어가게 됩니다.

let data1 = 1; //data1 = 1  
let data2 = data1; //data2 = 1 //data2라는 변수에 data1의 값을 복사해 넣어주었다.  
data2 = 2; //data2 = 2

data1 //1  
data2 //2  
//data1의 값에는 아무런 영향이 가지 않는다.

원시 타입 (Primitive Type)

  • number, boolean, null, undefined, string, bigint, symbol
  • 원시 값은 변경 불가능한 값이다.
  • 원시 값을 변수에 할당하면 변수에는 실제 값이 저장된다.
  • 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달된다. (값에 의한 전달)

참조 타입 (Reference Type)

  • 배열, 객체, 함수 등
  • 여러 데이터가 담기며, 데이터가 위치한 곳을 가리키는 주소가 변수에 저장된다.
  • 참조 타입의 값은 변경 가능한 값이다.
  • 객체를 변수에 할당하면 변수(확보된 메모리 공간)에는 참조 값이 저장된다.
  • 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. (참조에 의한 전달)

원시 자료형이 할당될 때에는 변수에 값(value) 자체가 담기고, 참조 자료형이 할당될 때는 보관함의 주소(reference)가 담깁니다. 그래서 참조 자료형은 기존에 고정된 크기의 보관함이 아니라, 동적으로 크기가 변하는 특별한 보관함을 사용할 수 있습니다. 두 타입의 가장 대표적인 차이로는 기본형에는 바로 값을 그대로 할당한다는 것이고 참조형에는 값이 저장된 주소값을 할당(참조)한다는 것입니다.

이러한 차이가 어떠한 의미를 지니는지 왜 이런 차이가 발생하는지를 코드가   실제 메모리에 저장되는 형태   를 통해 알아보도록 하겠습니다.


원시형(Primitive Type)

let a;
a = 10;

let b = 'abc';
b = false;

let c = b;
// b === c

c = 20;
// b !== c

※ 아래 구성된 표는 별개의 저장공간이 아니라 편의상 흐름도를 파악하기 위해 표기한 것.

let a;
변수 a 를 선언하면 컴퓨터는 메모리 안에 데이터를 담을 공간을 확보합니다. 아래 표에서는 임의로 100번째 주소로 공간을 확보했습니다.
변수명            
주소            
 
주소 ... 100 101 102 103 ...
데이터        -            

 

그 다음으로 확보한 주소값을 변수명 a와 매칭 시킵니다.
변수명            
주소  @100           
 
주소 ... 100 101 102 103 ...
데이터        -            
변수명   a            
주소  @100           
 
주소 ... 100 101 102 103 ...
데이터        -            

 

이후 코드 진행중에 a에 10을 할당하라는 명령을 만나면 컴퓨터는 변수 a를 찾아서 변수 a와 매칭되어 있는 주소값인  @100번을 읽고 그 주소로 이동해서 그 공간에 10을 집어 넣습니다
변수명   a            
주소  @100           
 
주소 ... 100 101 102 103 ...
데이터     10          

var b = 'abc';
이번에는 변수 b 를 선언하고 문자열 abc 를 할당하라는 명령어(var b = 'abc')를 내려봅니다.
우선 데이터가 담길 314번의 공간을 확보하고 확보된 공간의 주소값을 가지고 변수명 b 와 주소 @314에 매칭시킵니다.
변수명   a     b          
주소  @100  @101        
 
주소 ... 100 101 102 103 ...
데이터     10     -        

 

그리고 다시매칭된 주소의 314번으로 이동해서 문자열 'abc'를 저장합니다.
즉, let b = 'abc' 라는 문장은 앞서 실행했던 let a; a = 10; 를 하나로 합쳐놓은 문장에 불과합니다.
변수명   a     b          
주소  @100  @101        
 
주소 ... 100 101 102 103 ...
데이터     10     'abc'        

 

이러한 것을 선언과 할당이라고 합니다.

  • 선언 과정 : 공간을 확보하고 변수명과 주소를 매칭시키는 과정
  • 할당 과정 : 해당 변수가 가리키는 주소의 공간에 데이터를 저장하는 과정

 

b = false;
그 다음 문장에서 다시 b에 false 를 할당(b = false)하라고 명령하면 false 를 들고있는 채(let 라는 키워드가 없기 때문에)로 현재 가지고 있는 변수명들 중에 b 가 있는 지를 검색합니다. 만약 b 를 찾지 못하면 앞서와 마찬가지로 선언 과정을 거치게 됩니다. b 는 현재 메모리상에 존재하기 때문에 b 가 가리키는 주소(@101)값을 읽어서 해당 주소위치로 이동한 다음 그 자리에 false 를 덮어쓰게 됩니다.
변수명   a     b          
주소  @100  @101        
 
주소 ... 100 101 102 103 ...
데이터     10     false       

var c = b;
그리고 다음 문장에 let c = b; 을 수행하면 먼저 let 키워드가 있으니 선언과정부터 거치게 됩니다.
빈 공간인 102번을 확보하고 그 주소를 변수 c 와 매칭시킵니다. 그리고 314번으로 이동하여 b값인 false 값을 읽어옵니다.
그리고 읽어들인 값 false 를 가지고 c 를 찾아서 c가 가리키는 315번 공간에 false 를 넣게됩니다.
변수명   a     b    c       
주소  @100  @101  @102       
 
주소 ... 100 101 102 103 ...
데이터     10     false    false      
여기서 중요한 것은 이렇게 저장된 기본형 데이터들은 그 자체로 비교가 가능하다는 것입니다.
b 가 가리키는 메모리 공간상에 저장된 false 와 c 가 가리키는 메모리 공간상에 저장된 false 는 완전히 같은 값입니다.
false 라는 명칭 자체는 세상에 단 하나뿐인 키워드입니다. 우리는 이 하나뿐인 단어를 이곳저곳에서 사용하고 있는 것입니다.
하나뿐인 단어를 서로 비교하려고 하니까 당연히 완전히 같다(b === c)고 볼 수 밖에 없습니다.

c = 20;
다시 c 에 20을 할당하라(c = 20)고 하면 20을 가지고 c 가 저장된 메모리 공간을 찾아서 원래있던 false 대신 20을 넣습니다.
그런데 이 동작은 false 란 값이 20 으로 변하는 것이 아니라 메모리 공간에 있던 false 대신 20이란 값을 덮어씌워 버리는 것입니다.
이제 b 가 가리키는 곳의 값인 false 와 c 가 가리키는 공간의 값인 20 은 서로 완전히 다른 값이 되었으므로 b 와 c 는 같지않다.
(b !== c)라는 조건이 성립합니다.
변수명   a     b    c       
주소  @100  @101  @102       
 
주소 ... 100 101 102 103 ...
데이터     10     false    20      

 


 

참조형(Reference Type)

 참조형 데이터 중 대표적인 객체타입을 살펴보겠습니다.

let obj = {
    a : 1,
    b : 'b'
};

let obj2 = obj;
obj2.a = 10;
console.log(obj2.a); // 10
console.log(obj.a); // 10
기본형 타입과 마찬가지로 메모리 공간을 확보하고 주소를 변수명과 매칭시키는 과정은 동일합니다. (let obj)
변수명   obj  
         
주소   @22  
         
 
주소 ... 22 23 ... 600 601 602 ...
데이터      -               

 

다음으로 할당과정을 할 차례인데 할당을 하려고 보니 그 값이 기본형이 아니고 참조형입니다.( {a:1, b:'b'} )
참조형 데이터들은 프로퍼티(property)와 데이터(data), 즉 key : value로 묶인 쌍들로 이루어져 있습니다. 프로퍼티명은 변수와 비슷한 성질을 지니고 있고 변수명과 실제 데이터는 주소값을 통하여 연결되어 있는데 프로퍼티와 데이터 사이에서도 같은 동작을 합니다.
우선 각 프로퍼티명과 value 가 담길 주소를 매칭하기 위해서 공간을 새로 확보합니다. 여기선 새로 임의로 600번에 공간을 확보합니다.
변수명   obj            
주소   @22           
 
주소 ... 22 23 ... 600 601 602 ...
데이터   -       {                  }       
그리고 나서 601번에 a 프로퍼티의 value 가 담길 공간을 확보하고 확보된 주소를 a 프로퍼티와 매칭시킵니다.
변수명   obj            
주소   @22            
 
주소 ... 22 23 ... 600 601 602 ...
데이터     -     { a:@601 }    -      
그리고 602번에 b 프로퍼티의 value 가 담길 공간을 확보하고 그 할당된 주소의 602번을 b 프로퍼티와 매칭시킵니다.
변수명 obj          
주소 @22          
 
주소 ... 22 23 ... 600 601 602 ...
데이터   -       {               
   a:@601,  
   b:@602  
                } 
  -     -    
이제 다시 각각의 주소값마다 기본형 데이터값을 할당하게 됩니다. 601번에는 1 을 602번에는 'b' 가 저장됩니다.
변수명   obj            
주소   @22            
 
주소 ... 22 23 ... 600 601 602 ...
데이터   -       {               
   a:@601,  
   b:@602  
                } 
  1     'b'  

 

이제 앞서 어딘가에 저장되어 있을 객체 저장 정보의 주소 즉, 여기서는 600번을 22번에 매칭시키게 됩니다.
변수명   obj            
주소   @22            
 
주소 ... 22 23 ... 600 601 602 ...
데이터   @600       {               
   a:@601,  
   b:@602  
                } 
  1     'b'  
  • 이러한 과정을 데이터 공간의 기본형 데이터가 담길 때까지 반복하게 됩니다.
  • 즉, 참조형 데이터는 결과적으로 기본형 데이터들의 집합이라고 볼 수 있습니다.

var obj2 = obj;
그렇다면 이 상태에서 obj2 에 obj 를 할당하라고 하면 어떠한 일이 벌어질까요?
23번에 공간을 확보하고 obj2 와 매칭시킨 다음 obj 가 가리키고 있는 데이터인 600번이라고 하는 주소를 obj2 에 할당하게 됩니다.
변수명   obj    obj2         
주소   @22    @23         
 
주소 ... 22 23 ... 600 601 602 ...
데이터   @600 @601     {               
   a:@601,  
   b:@602  
                } 
  1     'b'  

이것이 바로 참조가 이루어지는 형식입니다. 객체는 어딘가에 따로 저장되어 있는데 그객체가 저장된 주소만을 복사해 온 것입니다.

그래서 obj2 의 a 프로퍼티(obj.a)에 10을 할당(obj2.a = 10;)하라고 하면 obj2 에 매칭된 23번 주소로 가서 다시 600번으로 이동한 다음 600번 안에서 a 프로퍼티를 찾고 a 와 매칭되어 있는 601번 주소로 이동하여 1 대신에 10 을 넣게됩니다.
변수명   obj    obj2         
주소   @22    @23         
 
주소 ... 22 23 ... 600 601 602 ...
데이터   @600 @601     {               
   a:@601,  
   b:@602  
                } 
  10     'b'  
여기서 당연하게도 obj2.a 값은 10이 나오는데 원본인 obj.a 값도 마찬가지로 10 이 출력됩니다.
obj2 객체가 obj 객체와 다른 새로운 객체를 만든 것이 아니라 본래 obj 가 바라보던 객체를 함께 바라보고 있기 때문입니다. 즉, obj === obj2 는 완벽히 동일한 객체를 참조한다고 말합니다.
그리고 이를 같은 곳을 바라보고 있다고 하여 포인터(pointer)가 같다 혹은 같은 포인터를 바라본다고 표현하기도 합니다.

 

 

참조 : codestates, webclub, mdn

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.