[JS] 원시타입, 객체타입

위영민(Victor)·2021년 8월 11일
0

개요

JS는 다른언어와 다르게, 원시값(문자열, 숫자 등)을 마치 객체(object)처럼 다룰 수 있기도 하다.
다른 말로는, 원시값에서도 객체에서처럼 메소드(Method)를 사용할 호출할 수 있다.
이번에는, JS에서의 원시값객체에 대해 조금 구분을 해본다.

본론

그전에, 잠시 알고가자.

동적(Dynamic) 타이핑

JS는 기본적으로 느슨한 타입(Loosely Typed)언어, 혹은 동적(Dynamic) 언어이다.
즉, 변수의 타입을 미리 선언할 필요가 없다는 뜻이다.
타입은 프로그램이 처리되는 과정(= 런타임(Runtime))에서 자동으로 파악될 것이다.
즉, 같은 변수에 여러 타입의 값을 넣을 수 있다는 뜻이기도 하다. (Typescript...매렵다.)

📍 JS 데이터 타입

ECMAScript 표준에서 다음 7(6+1)개의 자료형을 정의한다.

기본자료형(Primitive)

Object 를 제외한 모든 값은 변경 불가능한 값(Immutable)이다.

  • Number
  • String
  • Boolean
  • Symbol
  • Null
  • Undefined

객체형(Object)

프로퍼티(Property)에 다양한 종류의 값을 저장할 수 있다.
간단하게 { }(대괄호) 를 사용해서 객체를 생성할 수 있다.
JS에서는 여러 종류의 객체가 있는데, 함수도 객체의 일종이다.

  • Object
// 함수를 객체 프로퍼티에 저장한 예
 /**
   *  함수를 객체의 프로퍼티처럼 사용가능
   */
  let obj = {
    name: "min",
    sayHi: function () {
      console.log("안녕하세요 :)");
    },
  };

  obj.sayHi(); // 안녕하세요 :)

JS는 날짜, 오류, HTMLElement 등을 다룰 수 있게 해주는 다양한 내장 객체를 제공한다.
이런 객체들은 고유한 프로퍼티와 메서드를 가진다.

하지만, 이런 기능을 사용하면 시스템 자원이 많이 소모된다는 단점이 있다.

객체는 원시값보다 "무겁고", 내부 구조를 유지하기 위해 추가 자원을 사용하기 때문이다.


📍 원시값(Primitive)을 객체(Object)처럼 사용하기

  • 문자열이나 숫자와 같은 원시값을 다루어야 하는 작업이 많은데, 메소드를 사용하면 작업을 수월하게 할 수 있을 것 같다.
  • 하지만, 원시값은 가능한 한 빠르고 가벼워야 한다.

위에, 두 가지 이슈로 인해 JS 창시자는 다음과 같은 해결책을 생각해냈다고 한다.

  1. 원시값은 원시값 그대로 남겨둬, 단일 값 형태를 유지한다.
  2. 문자열, 숫자, 불리언, 심볼의 메소드와 프로퍼티에 접근할 수 있도록 언어 차원에서 허용한다.
  3. 이를 가능케 하기위해, 원시값이 메소드나 프로퍼티에 접근하려 하면, 추가 기능을 제공해주는 특수한 객체, "원시 래퍼 객체(Object Wrapper)"를 만들어 준다. 이 객체는 즉시 생성되고, 사용 후 삭제된다.

Object Wrapper는 원시 타입에 따라 종류가 다양하다.
각 Object Wrapper는 원시 자료형 이름에서 맨 앞글자만 대문자로 사용하면 된다. (Number, String, Boolean, Symbol ...)
각 Object Wrapper 마다 제공하는 메서드 역시 다르다.

예시를 보자.

  /**
   * Object Wrapper(래퍼 객체) 예시
   */
  let greeting = "Hello";
  console.log(greeting.toUpperCase()); // HELLO

언뜻 보기엔, 간단해보인다. 내부적으로 어떤 일이 일어나는지 보자.

  1. 문자열 greeting은 원시값(string)이므로, 원시값의 프로퍼티(toUpperCase( ))에 접근하는 순간, 특별한 객체(Object Wrapper)가 생성된다. 이 객체는 문자열의 값을 알고 있고, toUpperCase( )와 같은 유용한 메소드를 가지고 있다.

  2. 메소드가 실행되고, 새로운 문자열이 반환된다.

  3. 특별한 객체(Object Wrapper)는 삭제되고, 원시값 greeting 만 남는다.

이런 내부 프로세스를 통해, 원시값을 가볍게 유지하면서, 메소드를 호출할 수 있는 것이다.

JS 엔진은 위 프로세스의 최적화에 많은 신경을 쓴다. 원시 래퍼 객체(Object Wrapper)를 만들지 않고도, 마치 원리 래퍼 객체를 생성한 것 처럼 동작하게끔 해주는 것처럼 말이다.

📍 String, Number, Boolean 사용시 유의점

String, Number, Boolean생성자(new)로 쓰지 말자.

Java 등 몇몇 언어에서는 new Number(1)new Boolean(true) 같은 문법을 사용해 원하는 타입 "래퍼 객체"를 직접 만들 수 있다고 한다.

JS에서도 하위 호환성을 위해 이 기능을 남겨 두긴 했다.
하지만, 이 방식으로 래퍼 객체를 만드는 건 좋지 못하다.

몇 가지 혼동 사항이 있기 때문이다.

/**
   * JS에서, 원시값(특히, Number, String, Boolean)에 대한 생성자(new)를 통해 "래퍼 객체"를 만들 경우, 유의점
   */

  // 1. 같은 숫자인지 알아서, 타입을 까보니 다르다 !
  console.log(typeof 0); // number
  console.log(typeof new Number(0)); //	object 🔍

  // 2. 논리연산 시, 항상 "참"을 반환한다 !
  let zero = new Number(0);
  if (zero) {
    console.log(`zero : ${zero} ${typeof zero}`); // zero : 0 object 🔍(마지막 object 주의!)
  }

  // 예외) new(생성자 키워드)만 붙이지 않으면, object 생성으로 인식하지 않기 때문에 괜찮다. 🔍
  let one = Number("1");
  console.log(`one : ${one} ${typeof one}`); // one : 1 number 🔍(new 키워드가 없고, 단지 타입을 변환했고, 고로 'number'라는 타입으로 측정)

📍 Null 과 Undefined

  • 특수 자료형인 NullUndefined의 원시값(null, undefined)은 위에서 설명한 법칙에 따르지 않는다.

  • 그말은 즉, 연관된 "래퍼 객체(Object Wrapper)"도 없고, 메서드도 제공하지 않는다.

  • 어떤 의미로는, null 과 undefined 두 자료형이 가장 원시적이라고 할 수 있다.

결론

이번에 알아본 원시값객체 에 차이말고도, 데이터타입에 대해서는 알아야 할 것이 더 남아있다.

  • 객체타입에는 여러 개가 있다고 했는데, 그것에는 무엇들이 존재하는지

  • 빌트인 오브젝트(Built-In object) 란 무엇인지

이러한 주제를 차차 정리해보자.

다음은 주제는 깊은 복사(Deep Copy), 얕은 복사(Shallow Copy) 에 대해 정리한다.


참고

profile
블로그 이사했습니다 :) 👉 https://www.youngminss-log.com/blog

0개의 댓글