다음은 가장 많이 쓰는 네이티브 들이고, 네이티브는 사실 내장 함수이다.
- String()
- Number()
- Boolen()
- Array()
- Object()
- Function()
- RegExp()
- Date()
- Error()
- Symbol() : ES6에서 추가됨
필자는 Java를 주로 썼기 때문에 String()이 문자열 값을 생성하는생성자와 비슷하다고 생각했고,
아래와 같이 쓸 수 있다는 것을 알았다.
var s = new String("Hello World!");
console.log(s.toString()); // Hello World!
그러나, 네이티브는 생성자처럼 사용할 수 있지만 실제로 생성되는 결과물은 예상과 달랐다.
typeof s; // "object"
s instanceof String; // true
Object.prototype.toString.call(s); // "[object String]"
new String() 의 결과는 원시 값 "Hello World!"를 감싼 객체 래퍼다.
놀랍게도 typeof 연산자로 이 객체의 타입을 확인해보면 원시 값의 타입이 아닌 object의 하위 타입에 가깝다.
console.log(s);
// VM406:1 String {"Hello World!"}0: "H"1: "e"2: "l"3: "l"4: "o"5: " "6: "W"7: "o"8: "r"9: "l"10: "d"11: "!"length: 12 __proto__: String ... [[PrimitiveValue]]: "Hello World!"
내부 [[Class]]
typeof가 'object'인 값(배열 등)에는 [[Class]]라는 내부 프로퍼티가 추가로 붙는다.
이 프로퍼티는직접 접근할 수 없고, Obejct.prototype.toString() 라는 메서드에 값을 넣어 호출함으로써 존재를 알 수 있다.
대부분내부 [[Class]]는 해당 값과 관련된 내장 네이티브 생성자를 가리키지만, 그렇지 않을 때도 있다.
Null(), Undefined() 같은 네이티브 생성자는 없지만 내부 [[Class]] 값을 확인해보니 "Null", "Undefined" 이다.
이외에는 문자열, 숫자, 불리언 같은 단순 원시값은 이른바 '박싱, Boxing' 과정을 거친다.
즉, 단순 원시값은 해당 객체 래퍼로 자동 박싱됨을 알 수 있다.
예시 )
Object.prototype.toString.call([1,2,3]); // "[object Array]"
Object.prototype.toString.call(/regex-literal/i); // "[object RegExp]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call("123"); // "[object String]"
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
래퍼 박싱
원시 값엔 프로퍼티나 메서드가 없으므로 .length .toString()으로 접근하려면 원시 값을 객체 래퍼로 감싸줘야 한다.
다행히 자바스크립트는 원시 값을 알아서 박싱(래핑하므로 다음과 같이 사용된다.
var a = "test";
a.length; // 4
a.toUpperCase(); // "TEST"
이처럼 필요시 엔진이 알아서 암시적으로 박싱하게 되므로, 개박자가 직접 객체 형태로 사용하게 되지 않도록 하자.
예) new String("test") (X) ⇢ "test" (O)
그래도 혹시나 원시 값을 래퍼로 박싱하려면, 타입 객체 래퍼로 감싸게 되면 문제가 있다.
그러니 아래의 내용을 참고하여 Object()를 사용하여 객체 래퍼를 하자 !!
/** 객체 래퍼 주의사항 */
var a = new Boolean(false);
if (!a) {
console.log("Hello"); // 실행되지 않음
}
/** Object() 를 이용해 객체 래퍼 */
var b = false;
var c = Object(b);
typeof b; // "boolean"
typeof c; // "object"
b instanceof Boolean; // false
c instanceof Boolean; // true
Object.prototype.toString.call(b); // "[object Boolean]"
Object.prototype.toString.call(c); // "[object Boolean]"
if (!c.valueOf()) {
console.log("Hello"); // Hello
}
생성자
앞서 본 다른 네이티브도 그랬듯이, 확실히 필요해서 쓰는게 아니라면 생성자는 가급적 쓰지 않는 편이 좋다.
Array()
Array 생성자는 인자로 숫자를 하나 받으면.그 숫자를 원소로 하는 배열이 생성되는 것이 아닌 '배열의 크기를 미리 정하는' 기능이 있다.
게다가 배열의 크기를 미리 정하는 건 좋지 않아 그렇게 하려면 빈 배열을 만들고 나중에 length 프로퍼티에 숫자 값을 할당하는게 맞다.
var a = new Array(1, 2, 3);
a; // [1, 2, 3]
var b = [1, 2, 3];
b; // [1, 2, 3]
var c = new Array(3);
c.length; // 3
var d = [];
d.length = 3;
d; // [empty × 3]
Object(), Function(), ReqExp()
일반적으로 Object, Function, ReqExp 생성자도 선택 사항으로 어떤 분명한 의도가 아니라면 사용하지 않는 편이 좋다.
/** a, b 는 모두 같은 표현 */
var a = new Object();
a.test = "test";
a; // {test: "test"}
var b = {test : "test"};
b; // {test: "test"}
/** c, d, e 는 모두 같은 표현 */
var c = new Function("a", "return a * 2;");
var d = function(a) { return a * 2 };
function e(a) { return a * 2 };
/** f, g 는 모두 같은 표현 */
var f = new RegExp("^a*b+", "g");
var g = /^a*b+/g;
f.valueOf(); // /^a*b+/g
g; // /^a*b+/g
Object() 는 사용할 일이 없다.
이유는 위의 예시처럼 리터럴 형태로 한 번에 여러 프로퍼티를 지정할 수 있는데 굳이 하나씩 프로퍼티를 지정할 필요는 없으니까.
Function() 은 함수의 인자나 내용을 동적으로 정의할때 매우 드문 경우에 한해 유용하다.
단, eval()의 대용품이라고 생각하지 말자.
RegExp() 은 정규 표현식 패턴을 동적으로 정의할 경우 의미있는 유틸리티이다.
따라서 주로 new RegExp("패턴", "플래그") 형식으로 사용하자.
Date(), Error()
Date, Error 생성자는 리터럴 형식이 없어 다른 네이티브에 비해 유용하다.
date 객체 값은 new Date()로 생성되며, 이 생성자는 날짜/시각을 인자로 받고, 인자를 생략할 경우 현재 날짜/시각으로 대신한다.
date 객체는 유닉트 타임 스탬프 값(1970년 1월 1일 부터 현재까지 흐른 시간을 초 단위로 환산)을 얻는 용도로 가장 많이 쓰인다.
var a = new Date();
a.getTime(); // 1600181689739
Date.now() // 1600181689739
// ES5 이전 브라우저의 현재 시간
if (!Date.now) {
Date.now = function() {
return (new Date()).getTime();
};
}
error 객체는 주로 현재의 실행 Stack Context 를 포착하여 객체에 담는 것으로,
실행 Stack Context 는 디버깅에 도움이 될 정보들을 담고있다.
error 객체는 주로 throw 연산자와 함께 사용한다.
function test(x) {
if (!x) {
throw new Error("에러 발생");
}
}
test(false); // Uncaught Error: 에러 발생
Symbol()
symbol은 ES6부터 사용가능한 원시값이며, 충돌 염려 없이 객체 프로퍼티로 사용가능한 특별한 '유일 값'이다.
(단, 절대적으로 유일함이 보장되지는 않는다.)
symbol은 프로퍼티명으로 사용할 수 있으나, 프로그램 코드나 개발자 콘솔 창에서 symbol의 실제 값을 보거나 접근하는 건 불가능하다.
따라서 Symbol 객체의 정적 프로퍼티(Symbol.create, Symbol.iterator)로 접근 가능하다.
var a = Symbol("test");
a; // Symbol(test)
a.toString(); // "Symbol(test)"
typeof a; // "symbol"
var b = {};
b[a] = "test";
Object.getOwnPropertySymbols(b); //[Symbol(test)]
지금까지 많은 개발자가 "이건 전용/특수/내부 프로퍼티 입니다."의 용도로 언더스코어(_)가 앞에 붙은 프로퍼티 명도 언젠가는 Symbol에 의해 완전히 대체될 가능성이 높다.
프로토타입
내장 네이티브 생성자는 각자 .prototype 객체를 가진다.
prototype 객체에는 해당 객체의 하위 타입별로 고유한 로직이 담겨있다.
문서화 관례에 따라 prototype은 #로 줄여서 쓰기도 한다.
예시) String.prototype.X ⇢ String#X
각 생성자 프로토 타입마다 자신의 타입에 적합한기능이 구현되어 있고,
이 프로토 타입을 변경할 수도 있지만 이는 결코 바람직하지 않으니 변경은 하지 않도록 하자.
또한 변수에 적절한 타입의 값이 할당되지 않은 상태에서
Function.prototype = 빈 함수
RegExp.prototype = 빈 정규식
Array.prototype = 빈 배열
은 모두 좋은 '디폴트 값'이다.
이유는 .prototype 은 이미 생성되어 내장된 상태이므로 단 한번만 생성된다.
물론 이후에 변경될 디폴트 값으로 .prototype 을 변경하지 않았다는 전제하에서 말이다.ㅎㅎ
[타입(typeof)] chati.tistory.com/138
[값(value), 레퍼런스(reference)] chati.tistory.com/150
[참고] You Don't Know JS 타입과 문법, 스코프와 클로저 - 카일 심슨
'Javascript' 카테고리의 다른 글
[JavaScript] 느슨한(==)/엄격한(===) 동등 비교, 추상 관계 비교 (0) | 2020.09.18 |
---|---|
[JavaScript] 강제변환 - 암시적 강제변환, Implicit Coercion (0) | 2020.09.16 |
[JavaScript] 강제변환 - 명시적 강제변환, Explicit Coercion (0) | 2020.09.16 |
[JavaScript] 강제변환 - 추상 연산, Abstract operation (0) | 2020.09.16 |
[JavaScript] 값(value), 레퍼런스(Reference) (0) | 2020.09.15 |
[JavaScript] 타입(typeof) : 배열, 문자열, 숫자, 값이 아닌 값 (0) | 2020.07.31 |