자바스크립트에서 가장 많이 쓰는 네이티브
- String()
- Number()
- Boolean()
- Array()
- Object()
- Function()
- RegExp()
- Date()
- Error()
- Symbol()
네이티브는 생성자처럼 사용할 수 있지만 원시 값을 감싼 객체 래퍼이다.
let s = new String("Hello");
console.log(typeof s); // object -> string이 아니다
console.log(s); // [String: 'Hello']
내부 [[Class]]
typeof가 ‘object’인 값에는 [[Class]]라는 내부 프로퍼티가 추가로 붙는다. 이 프로퍼티는 직접 접근할 수 없고 Object.prototype.toString()
메서드에 값을 넣어 호출하여 볼 수 있다.
래퍼 박싱하기
자바스크립트는 원시 값을 알아서 박싱(래핑)하므로 다음과 같은 코드가 가능하다
let a = "hello";
a.length;
a.toUpperCase();
빈번하게 프로퍼티/.메서드를 사용해야 한다면 자바스크립트 엔진이 암시적으로 객체를 생성할 필요가 없도록 처음부터 값을 객체로 갖고 있는 것이 맞다고 생각할 수 있지만, 브라우저는 이런 경우를 스스로 최적화하기 때문에 직접 객체 형태로 선 최적화하면 프로그램이 더 느려질 수 있다.
new String("abc")
, new Number(42)
처럼 할 필요가 없다.
객체 래퍼의 함정
자바스크립트 엔진이 알아서 최적화하여 알아서 객체를 래핑하지만 사용해아한다면 유의할점이 있다.
다음과 같은 경우를 보자.
let a = new Boolean(false);
if (!a) {
console.log("!!!!!");
}
false
를 객체 래퍼로 감쌌지만 문제는 객체가 truthy
라는 점이다. 따라서 실행되지 않는다.
수동으로 원시 값을 박싱하려면 Object()
함수를 사용하자.
let a = new Boolean(false);
if (!a) {
console.log("!!!!!");
}
let b = "abc";
let c = new String("abcd");
let d = Object(b);
console.log(typeof b); // string
console.log(typeof c); // object
console.log(typeof d); // object
console.log(b instanceof String); // false
console.log(c instanceof String); // true
console.log(d instanceof String); // true
console.log(Object.prototype.toString.call(a)); // [object Boolean]
console.log(Object.prototype.toString.call(b)); // [object String]
console.log(Object.prototype.toString.call(c)); // [object String]
하지만 직접 박싱하는 방법은 지양하자.
언박싱
객체 래퍼의 원시 값은 valueOf()
메서드로 추출한다.
let a = new String("abc");
let b = new Number(42);
let c = new Boolean(true);
console.log(a.valueOf()); // abc
console.log(b.valueOf()); // 42
console.log(c.valueOf()); // true
3.4 네이티브, 나는 생성자다
배열, 객체, 함수, 정규식 값은 리터럴 형태로 생성하는 것이 일반적이지만, 리터럴은 생성자 형식으로 만든 것과 동일한 종류의 객체를 생성한다.
let a = new Array(3);
let b = [undefined, undefined, undefined];
let c = [];
c.length = 3;
console.log(a);
console.log(b);
console.log(c);
위 코드는 브라우저마다 결과가 다르게 나온다. 브라우저마다 다른 엔진을 사용하기 때문이다.
하지만 위와같은 빈 슬롯 배열을 애써 만들어서 사용하지말자.
Object(), Function(), RegExp()
Object()
, Function()
, RegExp()
생성자가 존재하긴하지만 분명한 의도가 있지 않다면 사용하지 않는편이 좋다.
let d = new Object();
d.foo = "bar";
let e = { foo: "bar" };
console.log(d); // { foo: 'bar' }
console.log(e); // { foo: 'bar' }
let f = new Function("a", "return a * 2;");
let g = function (a) {
return a * 2;
};
function h(a) {
return a * 2;
}
console.log(f(4)); // 8
console.log(g(4)); // 8
console.log(h(4)); // 8
let i = new RegExp("^a*b+", "g");
let j = /^a*b+/g;
console.log(i); // /^A*b+/g
console.log(j); // /^A*b+/g
new Object()
는 사용할 일이 없다.- Function 생성자는 함수의 인자나 내용을 동적으로 정의해야하는 매우 드문 경우에 사용
- 정규 표현식 생성자 역시 구문이 쉽고 자바스크립트 엔진이 실행 전 정규 표현식을 미리 컴파일한 후 캐시하는 성능상 이점이 있는 리터럴 형식을 사용하자, 역시 동적으로 정의해야할 경우가 아니고서는 사용하지않음
Date() and Error()
네이티브 생성자 Date()와 Error()는 리터럴 형식이 없다.
date 객체는 유닉스 타임스탬프 값을 얻는 용도로 쓰인다.
error 객체는 현재 실행 스택 콘텍스트를 포착하여 객체에 담는 것이다. 보통 throw 연산자와 함께 쓰이며, 디버깅에 도움이 될 만한 정보들을 담고있다.
Symbol()
Symbol
은 ES6에서 처음 선보인 새로운 원시 값 타입이다. 심벌은 충돌 염려 없이 객체 프로퍼티로 사용 가능한 특별한 ‘유일 값’이다. (유일함이 보장되지는 않음)
ES6 의 특수한 내장 로직에 쓰기 위해 고안되었지만 얼마든지 심벌을 정의할 수 있다.
심벌은 프로퍼티명으로 사용할 수 있으나 프로그램 코드나 개발자 콘솔 창에서 심벌의 실제 값을 보거나 접근하는 것은 불가능하다.
심벌은 전용 프로퍼티는 아니지만 Object.getOwnPropertySymbols()로 들여다보면 공용 프로퍼티임을 알 수 있다)
본래의 사용 목적에 맞게 대부분 전용 혹은 특별한 프로퍼티로 사용한다.
네이티브 프로토타입
내장 네이티브 생성자는 .prototype
객체를 가진다. prototype 객체에는 해당 객체의 하위 타입별로 고유한 로직이 담겨 있다.
변수에 적절한 타입의 값이 할당되지 않은 상태에서 Function.prototype
⇒ 빈 함수, RegExp.prototype
=> 빈 정규식, Array.prototype
⇒ 빈 배열은 모두 디폴트 값이다.
ES6 부터는 vals = vals || 디폴트 값
식의 구문 트릭은 더이상 필요없다. 함수 선언부에서 네이티브 구문을 통해 인자의 티폴트 값을 설정할 수 있기 때문이다.
프로토타입으로 디폴트 값을 세팅하면 .prototype
들은 이미 생성되어 내장된 상태이므로 단 한 번만 생성된다. 그러나 [], function(){}, /(?:)/
를 디폴트 값으로 사용하면 (엔진마다 다르지만) 함수를 호출할 때마다 디폴트 값을 다시 생성하므로 그만큼 메모리/CPU가 낭비된다.
그리고 이후에 변경될 디폴트 값으로 Array.prototype를 사용하는 일은 없어야한다. 어떤 식으로도 프로토타입을 변경하지 않도록 유의해야한다.