You Don't Know JS - 1.타입과 문법 - 3.네이티브
[ You Don’t Know JS 정리 ]
3. 네이티브
3.1 내부[class]
3.2 래퍼 박싱하기
3.2.1 객체 래퍼의 함정
3.3 언박싱
3.4 네이티브, 나는 생성자다
3.4.1 Array( )
3.4.2 Object( ), Function( ), and RegExp( )
3.4.3 Date( ) and Error( )
3.4.4 Symbol( )
3.4.5 네이티브 프로토타입
네이티브 = 내장 함수
가장 많이 쓰는 네이티브들
String()Number()Boolean()Array()Object()Function()RegExp()Date()Error()Symbol()
네이티브는 생성자처럼 사용할 수 있지만 실제로 생성되는 결과물은 예상과는 다르게 작동할 수 있다
1
2
3
4
5
6
7var a = new String("abc");
typeof a; //"object"
a instanceof String; // true
Object.prototype.toString.call(a); //"[object String]"new String("abc")생성자의 결과는 원시값"abc"를 감싼 객체 레퍼다.
이 객체의 타입을 확인해보면 자신이 감싼 원시 값의 타입이 아닌,object의 하위 타입에 가깝다.
3.1 내부 [Class]
typeof가 object인 값(배열 등)에는[Class]라는 내부 프로퍼티가 추가로 붙는다.이 프로퍼티는 직접 접근할 수 없고,
Object.prototype.toString()이라는 메서드에 값을 넣어 호출함으로써 존재를 엿볼 수 있다.1
2Object.prototype.toString.call([1,2,3]);
// "[object Array]"단순 원시 값(string, number, boolean)은 boxing과정을 거쳐 내부 프로퍼티를 생성
1
2Object.prototype.toString.call("abc");
// "[object String]"null,undefined도 네이티브 생성자는 없지만 내부 [Class]를 확인해보니 “Null”, “Undefined”가 나온다.1
2Object.prototype.toString.call(null)
// "[object Null]"
3.2 래퍼 박싱하기
원시 값에는 프로퍼티나 메서드가 없으므로
.length,.toString()으로 접근하려면 원시 값을 객체 래퍼로 감싸줘야 한다. 자바스크립트는 원시 값을 알아서 boxing한다.1
2
3
4var a = "abc";
a.length; //3
a.toUpperCase(); //"ABC"- 브라우저가 이러한 경우를 스스로 최적화하기 때문에 개발자가 직접 객체 형태로 ‘선 최적화’를 하면 프로그램이 더 느려질 수 있다.
- 즉,
new String("abc")처럼 코딩하지 말고, 원시값 “abc”를 사용하자
3.2.1 객체 래퍼의 함정
1 | var a = new Boolean(false); |
false를 객체 래퍼로 감쌌지만 문제는 객체가 ‘truthy’ 란 점이다. 그래서 예상과는 달리 안에 있는 false와는 반대의 결과다1
2
3
4
5
6
7
8
9
10var a = "abc";
var b = new String(a);
var c = Object(a);
typeof a; //"string"
typeof b; //"object"
typeof c; //"object"
b instanceof String; //true
c instanceof String; //true수동으로 원시 값을 박싱하려면
Object()함수를 이용하자 (앞에new키워드 없다)하지만 객체 래퍼로 직접 박싱하는 것을 권하지 않는다.
## 3.3 언박싱
객체 래퍼의 원시 값은
valueOf()메서드로 추출한다.1
2
3
4
5
6
7var a = new String("abc")
var b = new Number(42);
var c = new Boolean(true);
a.valueOf(); //"abc"
b.valueOf(); //42
c.valueOf();// true암시적인 언박싱이 일어난다.
1
2
3
4
5var a = new String("abc");
var b = a + ""; // b에는 언박싱된 원시 값 "abc"가 대입된다.
typeof a; //"object"
typeof b; //"string"
3.4 네이티브, 나는 생성자다
- 배열, 객체, 함수, 정규식 값은 리터럴 형태로 생성하는 것이 일반적 (생성자 형식으로 만든 것과 동일한 종류의 객체를 생성)
- 생성자는 가급적 쓰지 않는 편이 좋다.
3.4.1 Array()
Array()생성자 앞에new키워드를 붙이지 않아도 된다.Array()생성자에는 특별한 형식이 하나 있는데 인자로 숫자를 하나만 받으면 그 숫자를 원소로하는 배열을 생성하는 것이 아니라 배열의 크기를 미리 정하는 기능이다.1
2
3
4var a = new Array(3);
a.length; //3
a; // [undefined x 3]
// 슬롯 자체가 존재하지 않아 배열 슬롯에 3개의 undefined를 밀어 넣은 것브라우저마다 개발자 콘솔창에서 객체를 나타내는 방식이 제각각이라 더 혼란스러움
1
2
3
4
5
6
7
8var a = new Array(3);
var b = [undefined, undefined, undefined];
var c = [];
c.length = 3
a;
b;
c;chrome
- a =
[undefined x 3] - b =
[undefined, undefined. undefined] - c =
[undefined x 3]
- a =
firefox
- a =
[ , , , ,] - b =
[undefined, undefined. undefined] - c =
[ , , , ,](ES5부터 후미 콤마trailing comma 를 허용했기 때문)- 최근에는 Array[< 3 empty slots>]
- 최근에는 Array[< 3 empty slots>]
- a =
.join()/.map()1
2
3
4
5
6
7
8var a = new Array(3);
var b = [ undefined, undefined, undefined ];
a.join("-"); // "--"
b.join("-"); // "--"
a.map(function(v,i) {return i;}); // [undefined x3]
b.map(function(v,i) {return i;}); // [0,1,2]join()은 슬롯이 있다는 가정하에 length 만큼 루프를 반복한다.a.map()은 a에 슬롯이 없기 때문에map()함수가 순회할 원소가 없다.join()처럼 가정을 하지 않기 때문에 ‘빈 슬롯’배열이 입력되면 예기치 않은 결과가 빚어지거나 실패의 원인이 된다.
손으로 입력하지 않고 진짜
undefined로 채워진 배열 생성하는 법1
2var a = Array.apply(null, {length: 3});
a; //[undefined, undefined, undefined]apply()는 모든 함수에서 사용 가능한 유틸리티로 첫 번째 인자this는 객체 바인딩, 두 번째 인자는 인자의 배열로 배열 안에 포함된 원소들이 spread 되어서 함수의 인자로 전달된다.- 따라서
Array.apply()는Array()함수를 호출하는 동시에{length : 3}객체 값을 펼쳐 인자로 넣는다. apply()내부에서는 0에서 length 직전까지 루프를 순회할 것이다.- 인덱스별로 객체에서 키를 가져온다. 만약 함수 내부에서 배열 객체 인자를
arr라고 명명한다면 프로퍼티는arr[0], arr[1], arr[2]로 접근할 것이다. 이 세 프로퍼티 모두{length : 3 }객체에는 존재하지 않기 때문에 모두undefined를 반환할 것이다. - 결국
Array()를 호출하면Array(undefined, undefined, undefined)처럼 되어서 빈 슬롯이 아닌undefined로 채워진 배열이 탄생한다. - 하지만 절대로 이런 빈 슬롯 배열을 만들어서 사용하지 말자
3.4.2 Object(), Function(), and RegExp()
- 셋 다 선택사항 (어떤 분명한 의도가 있는 것이 아니라면 사용하지 않는 편이 좋다.)
new Object(): 리터럴 형태로 한 번에 여러 프로퍼티를 지정할 수 있는데 굳이 한번에 하나씩 일일이 프로퍼티를 지정하는 방법으로 돌아가야할까?Function(): 함수의 인자나 내용을 동적으로 정의해야하는 매우 드문 경우에 한해 유용하다.RegExp(): 리터럴 형식으로 정의할 것을 적극 권장. 구문이 쉽고 무엇보다 성능상 이점(자바스크립트 엔진이 실행 전 정규 표현식을 미리 컴파일한 후 캐시한다.)이 있다.
3.4.3 Date() and Error()
- 둘 다 리터럴 형식이 없으므로 다른 네이티브에 비해 유용
new Date(): 날짜/ 시각을 인자로 받는다. 생략하면 현재 날짜/ 시각new키워드 없이Date()를 호출하면 현재 날짜/시각에 해당하는 문자열을 반환
Error():new키워드가 있든 없든 결과는 같다.- error 객체의 주 용도는 현재의 실행 스택 콘텍스트를 포착하여 객체에 담는 것
- 실행 스택 콘텍스트에는 함수 호출 스택, error 객체가 만들어진 줄 번호 등 디버깅에 도움이 될 정보들이 있다
- 보통 throw 연산자와 함께 사용된다.
- Error 객체 인스턴스에는 message 프로퍼티는 기본으로 들어 있고, type 등 다른 프로퍼티가 포함되어 있을 때도 있다.
1
2
3
4
5
6
7var a = new Error('2');
console.dir(a);
/*
Error : 2
message: "2"
*/
3.4.4 Symbol()
- ES6에서 처음 선보인 새로운 원시 값 타입 (객체 아님)
- 충돌 염려 없이 객체 프로퍼티로 사용 가능한 특별한 ‘유일 값’ (절대적으로 보장되지는 않음)
- 직접 정의하려면
Symbol()네이티브를 사용한다.new를 붙이면 에러가 나는 유일한 네이티브 생성자이다.
### 3.4.5 네이티브 프로토타입 - 내장 네이티브 생성자는 각자의
.prototype객체를 가진다.- Prototype 객체에는 해당 객체의 하위 타입별로 고유한 로직이 담겨 있다.
- 문자열 원시 값을 (박싱으로) 확장한 것까지 포함하여 모든 String 객체는 기본적으로
String.prototype객체에 정의된 메서드에 접근할 수 있다.
- 프로퍼티를 추가하지 않고 네이티브 프로토타입을 변경할 수도 있지만 결코 바람직하지 않다.
- 프로토타입은 디폴트다
- 변수에 적절한 타입의 값이 할당되지 않은 상태에서
Function.prototype-> 빈 함수RegExp.prototype-> 빈 정규식Array.prototype-> 빈 배열
- ES6부터는
vals = valse || 디폴트 값식의 구문 트릭을 더 이상 사용하지 않아도 된다. 함수 선언부에서 네이티브 구문을 통해 인자의 디폴트 값을 설정할 수 있기 때문
- 변수에 적절한 타입의 값이 할당되지 않은 상태에서