[JS 33가지 개념] 2. Primitive Types

2020. 6. 23. 20:34Javascript/33가지 개념

1. Primitive Type

 

 Primitive라는 단어를 사전에서 찾아보면 원시적인, 초기의라는 뜻이 나온다. 지금 생각해보면 왜 이런 단어선택을 했는지는 이해는 안간다. 본론으로 돌아와서, 자바스크립트Primitive TypeReference Type이라는 두 가지 형태의 자료형이 존재한다. Primitive Type는 총 여섯 개다.

 

  1. true or false -> boolean type

  2. null -> 존재하지 않음을 정의

  3. undefined -> 존재하지 않음

  4. number -> 정밀한 64비트의 float형 자료. 자바스크립트에서는 정수형이라는 자료형태가 없다.

  5. string -> 문자형 자료

  6. symbol(ES6에서 나온 새로운 자료형)

 

 여기서 null과 undefined를 헷갈려 하는 사람들이 많다. 뜻은 둘 다 존재하지 않음을 의미한다. 하지만 null과 undefined의 차이는 정의가 됐고 안됐고의 차이다. 

const a;
const b = null;

console.log(a);	// undefined
console.log(b);	// null

 

 a를 출력하면 undefined를 출력한다. 이는 존재하지 않다라는 것을 의미한다. 이런 출력 값을 얻은 이유는 값을 정해주지 않았기 때문이다. 하지만 b의 경우는 다르다. null이라는 값을 부여했고, b를 출력해주니 부여해준 null이라는 값이 있다는 것을 우리에게 보여줬다. 좀 더 쉽게 설명하자면 undefined머뭇거리다가 물건을 못넣은 것, null물건을 넣지 않기로 자의적으로 결정한 것이라고 보면 될 것 같다.

 

 

본론으로 돌아와서 Primitive Type(원시 타입)에 대하여 추가적인 설명을 하도록 하겠다.

원시 타입은 메소드를 가지지 않는다.

null.toString 같은 형태의 함수를 본 적 없는 것처럼 말이다.

메소드가 없기 때문에 원시타입immutable(변하지 않음)의 속성을 가진다. 

 

 

 하지만 값을 재할당할 순 있다. 그 값은 변하는 값이 아니라 새로운 값 자체를 할당하기 때문에 원시 타입(Primitive) 타입은 참조 타입이 아니라 값을 저장하는 타입이라 할 수 있다. 다음 예제를 보자.

/* (1) */
let cat = "cat";
cat = "tiger";
console.log(cat);

/* (2) */
let name = "jerry";
name[0] = "J";
console.log(name);

 

 위 예제에는 (1), (2)의 상황이 존재한다. (1)의 상황은 값을 새로 재할당한 경우를 의미한다. 따라서 cat을 출력하면 "tiger"라는 문자열이 출력된다. 하지만 (2)의 경우는 다르다. 문자열은 Primitive Type이므로 immutable(변하지 않음)의 속성을 가졌기 때문에 "jerry"라는 문자열이 그대로 출력된다. 따라서 primitive 타입은 값을 저장하고 새로운 값을 재할당할 순 있지만, 변경할 수 없는 immutable한 특성을 가지고 있다는 것을 알 수 있다.

 

 

2. Reference Type

 

 자바스크립트는 Primitive Type말고도 Reference Type(참조 타입)을 가지고 있다. 참조 타입의 형태Object 객체이며, Object(객체), Array(배열), function(함수) 총 세 가지의 형태를 가지고 있다. Reference Type은 값을 저장하지 않고, 참조 값(주소)을 저장한다. 그렇다면 참조 값을 저장한다는 것은 무엇을 의미하는 걸까? 밑에 예시를 보자.

/* Reference Type 종류 */
console.log(typeof []); //object
console.log(typeof {}); //object
console.log(typeof function(){}); //function, 자료형이 function으로 출력되나 사실상 객체가 맞다.

/* example (1) */
const x = {num: 10};
const y = x;

x.num++;

console.log(x.num);
console.log(y.num);

 

 (1) 이라는 예시를 보자. 결과적으로 x, y의 num 모두 11을 출력한다. 그 이유는 y가 x값을 참조하기 때문이다. 참조한다는 것은 그것을 내걸로 만드는 것이 아니라 잠깐 빌리는 것이라 생각하면 된다. 책을 빌려서 내가 가지고 있긴 하지만, 빌린 책은 내 소유물이 아니라 도서관의 소유물이기 때문이다. 따라서 y도 x라는 주소 값을 빌려왔을 뿐 x라는 독립적인 값을 부여받은 것이 아니다. 그래서 참조 타입은 각기 고유의 주소를 가지고 있고, 서로 비교할 때 고유의 주소값을 비교한다. 다음 예제를 보면 알 것이다.

console.log(1 === 1); // true;
console.log("dog" === "dog"); // true;

console.log({} === {}); //false;
console.log([] === []); //false;

 

 {}과 {}, []와 [] 두 값은 같아보여도 서로가 참조하는 주소 값이 다르기 때문에 false라는 결과가 나온다. 하지만 Primitive Type의 경우 값만을 가지고 비교하기 때문에 윗쪽의 두 예시 모두 true라는 결과값을 얻게된다.

 

 

3. Function

함수는 Constructor(생성자) Call(호출)과 같은 속성을 가진 객체다. 또한 함수는 보통 객체처럼 함수도 새로운 속성 값을 다음과 같이 추가할 수 있다(여기서 Constructor는 객체의 프로타입을 만드는 함수를 지정하는 것을 말하는데 이 부분에 대해서는 나중에 따로 포스팅하도록 하겠다.).

const func = function(num){};

console.log(func.name); // 함수 이름 출력
console.log(func.length); // 매개 변수에 개수에 따라 길이가 달라진다.
func.num = 2;
console.log(func.num); // 2

 

 또한 함수는 객체 중에서도 1급 객체이다. 왜냐하면

  1. 인자를 넘길 수 있다.

  2.  변수나 배열에도 할당이 가능하다.

  3.  반환 값으로도 넘길 수 있다. 

이 세가지 이유 때문이다. 함수를 생성자로 사용하고 싶을때는 무조건 new 키워드를 붙여서 사용해야한다. 그렇지 않으면 this글로벌 컨텍스트인 window를 가리킨다.

/* 생성자로 사용하지 않았기 때문에 this는 글로벌 컨텍스트인 window 객체를 출력*/ 
function func1(name){
  console.log("Point:",this);
}

func1.call(); //window 객체 출력


/* 생성자로 사용했기 때문에 this는 현재 컨텍스트인 func2 객체 출력 */
const func2 = function(){
  console.log("Point:",this);
}

const func3= new func2();

 

 

4. Wrapper Objects

  Wrapper Objects는 원시 타입(Primitive)에 대한 생성자이다. 문자열을 예로 들 때, String이라는 전역 객체를 직접 사용해서 다음과 같이 문자열을 생성할 수 있다. 

const str = new String("str");

 

 그리고 Wrapper Objects를 생성자 함수 처럼 사용할 수 있다.

const str = new String("str");
console.log(typeof str); //object
console.log(str === "str"); //false

 

 이 생성자 객체는 다음과 같은 속성으로 이루어져 있다.

{
  '0': 's',
  '1': 't',
  '2': 'r',
  'length': 3
}

 

 

5. 오토 박싱(Auto-boxing)

 자바스크립트에서는 따옴표를 이용해서 문자열을 만들든 생성자를 이용해서 만들든간에 어쨋든 String 객체를 사용하여 문자열을 반환한다(다른 원시 타입의 경우도 마찬가지다). 놀라운 것은 .constructor를 이용해서 생성자의 속성을 확인할 수 있다는 것이다. 그런데 아까 우리는 원시 타입(Primitive Type)은 메서드를 가질 수 없다고 했다. 무슨 일이 일어난 것일까?

const str = new String("str");
console.log(typeof str); //object
console.log(str.constructor); //Function: String
console.log(str.constructor === String); //true

 

 이 경우에는 오토박싱이라는 일이 발생한다. 특정한 원시타입에서 속성이나 메소드를 호출하려할 때 자바스크립트는 임시적으로 Wrapper Object로 바꾼 후에 속성이나 메소드에 접근하려한다. 이러한 접근을 우리는 오토박싱이라고 한다. 그래서 우리는 문자열을 생성자 없이 선언해도 length 속성을 볼 수 있는 것이다.

const str = "str"; // 생성자 없이 선언

console.log(str.length);
// 자바스크립트가 임의적으로 str을 Wrapper Object로 변환해서 length 속성에 접근해서
// 3이라는 값을 출력

 

 length 속성에 접근 한 후, length 값을 이용하면 자바스크립트는 임의적으로 생성한 Wrapper Objects를 지워버린다. 따라서 아래와 같은 코드는 임의로 위와 같은 로직으로 실행되기 때문에 코드에 에러가나지 않는다.

const num = 123;
num.check = "true"; //임의적으로 Number라는 Wrapper Object를 생성해서 check이라는 속성에 접근해서 true값을 부여
num.check // 이미 check값에 접근해서 이용했으므로 undefined가 뜸

 

 하지만 wrapper Object가 없는 null, undefined의 경우 속성에 접근하려고 하면, 오토 박싱을 할 수 없기 때문에 에러메세지를 출력한다.

const str = null
str.check = true;
console.log("출력:", str.check);

 

 

6. 참고자료