[JS 33가지] 5. typeof vs instanceof

2020. 6. 30. 20:35Javascript/33가지 개념

1. typeof

typeof 연산자는 피 연산자의 자료형을 나타내는 문자열을 반환하는 함수다.

console.log(typeof 42);     // number
console.log(typeof "str");  // string
console.log(typeof true);   // boolean
console.log(typeof false);  // boolean
console.log(typeof function(){}) // function
console.log(typeof {});     // object
console.log(typeof []);     // object

 

 

 참조 타입이든 원시 타입이든 자료형을 나타내는데 크게 문제가 없는 것 같다. 하지만 typeof의 문제점null의 자료형을 나타내는 문자열을 반환하는데서 존재한다.

console.log(typeof null); // object

 

 

 object? object? 이런 말도 안되는 결과가.. 이것은 맞는 것이 아니다. 틀린거다. 그런데 안타깝게도 이 문제는 아직 수정되지 않았다. 그 이유는 기존에 있던 자바스크립트 코드를 깨버릴 수 있기 때문이다. 이 결과가 object를 나타내는 것은 초기 자바스크립트에서 정해졌다. 자바스크립트 초기 버전에서의 값(value) 32비트type tag실제 데이터로 구성되어있었고, 그 형태로 저장되었다. 특히 type tag매우 적은 비트로 저장되었으며, 다섯 종류가 있었다.

 

  1. 000: 객체(object), 객체에 저장된 데이터는 객체에 대한 참조 값

  2. 1: int, int는 31비트의 부호(+, -)가 있는 정수 값

  3. 010: double, double 데이터는 부동 소수점에 대한 참조 값

  4. 100: string, string 데이터는 문자열에 대한 참조 값

  5. 110: boolean, boolean 값은 그냥 boolean.

 

3비트밖에 안됐던게 너무나도 놀랍다. 그런데 두 개의 특별한 값이 있었다.

  1. undefined
  2. Null

다음 자바스크립트 엔진 코드를 보면, 왜 typeof null이 object를 출력하는지 알 수 있을 것이다.

/* 출처: https://2ality.com/2013/10/typeof-null.html */
JS_PUBLIC_API(JSType)
    JS_TypeOfValue(JSContext *cx, jsval v)
    {
        JSType type = JSTYPE_VOID;
        JSObject *obj;
        JSObjectOps *ops;
        JSClass *clasp;

        CHECK_REQUEST(cx);
        if (JSVAL_IS_VOID(v)) {  // (1)
            type = JSTYPE_VOID;
        } else if (JSVAL_IS_OBJECT(v)) {  // (2)
            obj = JSVAL_TO_OBJECT(v);
            if (obj &&
                (ops = obj->map->ops,
                 ops == &js_ObjectOps
                 ? (clasp = OBJ_GET_CLASS(cx, obj),
                    clasp->call || clasp == &js_FunctionClass) // (3,4)
                 : ops->call != 0)) {  // (3)
                type = JSTYPE_FUNCTION;
            } else {
                type = JSTYPE_OBJECT;
            }
        } else if (JSVAL_IS_NUMBER(v)) {
            type = JSTYPE_NUMBER;
        } else if (JSVAL_IS_STRING(v)) {
            type = JSTYPE_STRING;
        } else if (JSVAL_IS_BOOLEAN(v)) {
            type = JSTYPE_BOOLEAN;
        }
        return type;
    }

 

 위 코드의 순서는 다음과 같다.

 

 1. (1)에서 자바스크립트 엔진은 먼저 undefined의 유무를 검사한다.

 #define JSVAL_IS_VOID(v)  ((v) == JSVAL_VOID)

 

 2. (2)에서 자바스크립트 엔진은 value(값)가 객체 타입을 가지고 있는지의 유무를 검사하고, 추가적으로 값에 내부 속성이 있거나, 호출이 가능하면 함수로 판단한다[(3), (3, 4)]. 둘 다 없을 경우, 객체가 된다. 따라서 null은 호출이 가능하지도 않고, 내부 속성을 가지고 있지 않기 때문에 object를 반환하는 것이다.

 

 

 3. 마지막 단계로 값의 자료형(String, Boolean, Number)를 판단한다. 하지만, Null을 판단하는 과정은 따로 존재하지 않는다. 따라서 null은 계속 object로 남게된다.

 

 

 여기 까지 썼던 내용에 대한 정리는 다음과 같다.

 

typeof는 null을 제외한 원시 타입을 체크하는데는 문제가 없지만, 객체의 종류까지는 체크하는 것은 불가능하다.

 

 

2. Object.prototype.toString

 하지만, Object.prototype.toString을 이용하면, 객체의 종류까지 체크하는 것이 가능하다. 여기서 Object.prototype.toString 메소드는 객체를 나타내는 문자열을 반환하는 함수다.

const obj = new Object();
console.log(obj.toString());

 

 

 Funtion.prototype.call 메소드를 사용하면 모든 타입의 값의 타입을 알아낼 수 있다.

Object.prototype.toString.call('');             // [object String]
Object.prototype.toString.call(new String());   // [object String]
Object.prototype.toString.call(1);              // [object Number]
Object.prototype.toString.call(new Number());   // [object Number]
Object.prototype.toString.call(NaN);            // [object Number]
Object.prototype.toString.call(Infinity);       // [object Number]
Object.prototype.toString.call(true);           // [object Boolean]
Object.prototype.toString.call(undefined);      // [object Undefined]
Object.prototype.toString.call();               // [object Undefined]
Object.prototype.toString.call(null);           // [object Null]
Object.prototype.toString.call([]);             // [object Array]
Object.prototype.toString.call({});             // [object Object]
Object.prototype.toString.call(new Date());     // [object Date]
Object.prototype.toString.call(Math);           // [object Math]
Object.prototype.toString.call(/test/i);        // [object RegExp]
Object.prototype.toString.call(function () {}); // [object Function]
Object.prototype.toString.call(document);       // [object HTMLDocument]
Object.prototype.toString.call(argument);       // [object Arguments]
Object.prototype.toString.call(undeclared);     // ReferenceError

 

 이 메소드를 함수로 나타내면 다음과 같다.

function getType(target){
  return Object.prototype.toString.call(target).slice(8, -1);
}

getType("123"); // String
getType([]);    // Array
getType(null);  // Null
getType(true);  // true
getType(new Date()) // Date

 

 

3. instanceof 

 instanceof 연산자는 생성자의 prototype 속성이 객체의 프로토 타입에 존재하는지를 판별하는 연산자다. instanceof는 앞에 두 연산자와 다르게 객체 타입에서만 작동한다는 차이점이 있다. 

console.log([] instanceof Object); // true
console.log([] instanceof Array);  // true
console.log({} instanceof Array);  // false 

 

 

4. 요약

  • typeof는 null을 제외하곤 원시 타입의 자료형을 반환할 수 있다. 하지만 object의 종류까지 반환은 불가능하다.
  • Object.prototype.toString, Object.prototype.Call 두 객체 내부 속성을 이용하면 NaN을 제외하고, 원시, 참조 타입 종류까지 반환할 수 있다.
  • instanceof 연산자은 객체 타입에서만 작동한다.

 

 

5. 참고 자료