JS는 다른언어와 다르게, 원시값(문자열, 숫자 등)을 마치 객체(object)처럼 다룰 수 있기도 하다.
다른 말로는, 원시값에서도 객체에서처럼 메소드(Method)를 사용할 호출할 수 있다.
이번에는, JS에서의 원시값 과 객체에 대해 조금 구분을 해본다.
그전에, 잠시 알고가자.
동적(Dynamic) 타이핑
JS는 기본적으로 느슨한 타입(Loosely Typed)언어, 혹은 동적(Dynamic) 언어이다.
즉, 변수의 타입을 미리 선언할 필요가 없다는 뜻이다.
타입은 프로그램이 처리되는 과정(= 런타임(Runtime))에서 자동으로 파악될 것이다.
즉, 같은 변수에 여러 타입의 값을 넣을 수 있다는 뜻이기도 하다. (Typescript...매렵다.)
ECMAScript 표준에서 다음 7(6+1)개의 자료형을 정의한다.
기본자료형(Primitive)
Object 를 제외한 모든 값은 변경 불가능한 값(Immutable)이다.
객체형(Object)
프로퍼티(Property)에 다양한 종류의 값을 저장할 수 있다.
간단하게 { }(대괄호) 를 사용해서 객체를 생성할 수 있다.
JS에서는 여러 종류의 객체가 있는데, 함수도 객체의 일종이다.
// 함수를 객체 프로퍼티에 저장한 예
/**
* 함수를 객체의 프로퍼티처럼 사용가능
*/
let obj = {
name: "min",
sayHi: function () {
console.log("안녕하세요 :)");
},
};
obj.sayHi(); // 안녕하세요 :)
JS는 날짜, 오류, HTMLElement 등을 다룰 수 있게 해주는 다양한 내장 객체를 제공한다.
이런 객체들은 고유한 프로퍼티와 메서드를 가진다.
하지만, 이런 기능을 사용하면 시스템 자원이 많이 소모된다는 단점이 있다.
객체는 원시값보다 "무겁고", 내부 구조를 유지하기 위해 추가 자원을 사용하기 때문이다.
위에, 두 가지 이슈로 인해 JS 창시자는 다음과 같은 해결책을 생각해냈다고 한다.
Object Wrapper는 원시 타입에 따라 종류가 다양하다.
각 Object Wrapper는 원시 자료형 이름에서 맨 앞글자만 대문자로 사용하면 된다. (Number, String, Boolean, Symbol ...)
각 Object Wrapper 마다 제공하는 메서드 역시 다르다.
예시를 보자.
/**
* Object Wrapper(래퍼 객체) 예시
*/
let greeting = "Hello";
console.log(greeting.toUpperCase()); // HELLO
언뜻 보기엔, 간단해보인다. 내부적으로 어떤 일이 일어나는지 보자.
문자열 greeting은 원시값(string)이므로, 원시값의 프로퍼티(toUpperCase( ))에 접근하는 순간, 특별한 객체(Object Wrapper)가 생성된다. 이 객체는 문자열의 값을 알고 있고, toUpperCase( )와 같은 유용한 메소드를 가지고 있다.
메소드가 실행되고, 새로운 문자열이 반환된다.
특별한 객체(Object Wrapper)는 삭제되고, 원시값 greeting 만 남는다.
이런 내부 프로세스를 통해, 원시값을 가볍게 유지하면서, 메소드를 호출할 수 있는 것이다.
JS 엔진은 위 프로세스의 최적화에 많은 신경을 쓴다. 원시 래퍼 객체(Object Wrapper)를 만들지 않고도, 마치 원리 래퍼 객체를 생성한 것 처럼 동작하게끔 해주는 것처럼 말이다.
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의 원시값(null, undefined)은 위에서 설명한 법칙에 따르지 않는다.
그말은 즉, 연관된 "래퍼 객체(Object Wrapper)"도 없고, 메서드도 제공하지 않는다.
어떤 의미로는, null 과 undefined 두 자료형이 가장 원시적이라고 할 수 있다.
이번에 알아본 원시값 과 객체 에 차이말고도, 데이터타입에 대해서는 알아야 할 것이 더 남아있다.
객체타입에는 여러 개가 있다고 했는데, 그것에는 무엇들이 존재하는지
빌트인 오브젝트(Built-In object) 란 무엇인지
이러한 주제를 차차 정리해보자.
다음은 주제는 깊은 복사(Deep Copy), 얕은 복사(Shallow Copy) 에 대해 정리한다.