[ 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
    7
    var 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
    2
    Object.prototype.toString.call([1,2,3]);
    // "[object Array]"
  • 단순 원시 값(string, number, boolean)은 boxing과정을 거쳐 내부 프로퍼티를 생성

    1
    2
    Object.prototype.toString.call("abc");
    // "[object String]"
  • null, undefined도 네이티브 생성자는 없지만 내부 [Class]를 확인해보니 “Null”, “Undefined”가 나온다.

    1
    2
    Object.prototype.toString.call(null)
    // "[object Null]"

3.2 래퍼 박싱하기

  • 원시 값에는 프로퍼티나 메서드가 없으므로 .length, .toString()으로 접근하려면 원시 값을 객체 래퍼로 감싸줘야 한다. 자바스크립트는 원시 값을 알아서 boxing한다.

    1
    2
    3
    4
    var a = "abc";

    a.length; //3
    a.toUpperCase(); //"ABC"
    • 브라우저가 이러한 경우를 스스로 최적화하기 때문에 개발자가 직접 객체 형태로 ‘선 최적화’를 하면 프로그램이 더 느려질 수 있다.
    • 즉, new String("abc") 처럼 코딩하지 말고, 원시값 “abc”를 사용하자

3.2.1 객체 래퍼의 함정

1
2
3
4
5
var a = new Boolean(false);

if (!a) {
console.log("Oops");
}
  • false를 객체 래퍼로 감쌌지만 문제는 객체가 ‘truthy’ 란 점이다. 그래서 예상과는 달리 안에 있는 false와는 반대의 결과다

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var 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
    7
    var 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
    5
    var 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
    4
    var a = new Array(3);
    a.length; //3
    a; // [undefined x 3]
    // 슬롯 자체가 존재하지 않아 배열 슬롯에 3개의 undefined를 밀어 넣은 것
  • 브라우저마다 개발자 콘솔창에서 객체를 나타내는 방식이 제각각이라 더 혼란스러움

    1
    2
    3
    4
    5
    6
    7
    8
    var 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]
  • firefox

    • a = [ , , , ,]
    • b = [undefined, undefined. undefined]
    • c = [ , , , ,] (ES5부터 후미 콤마trailing comma 를 허용했기 때문)
      • 최근에는 Array[< 3 empty slots>]
  • .join() / .map()

    1
    2
    3
    4
    5
    6
    7
    8
    var 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
    2
    var 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
      7
      var 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 || 디폴트 값 식의 구문 트릭을 더 이상 사용하지 않아도 된다. 함수 선언부에서 네이티브 구문을 통해 인자의 디폴트 값을 설정할 수 있기 때문