개발하다 보면 변수가 정확히 어떤 타입인지 확인해야 할 순간이 자주 온다. 다만 대부분의 개발자들은 typeof 하나만 쓰다가 예상 못 한 결과에 당황하곤 한다. 특히 배열이나 null을 체크할 때 typeof가 제대로 된 결과를 주지 않는다는 걸 모르는 경우가 많다. 이번에는 JavaScript의 세 가지 타입 체킹 방식을 정확히 뭔지, 언제 써야 하는지, 각각의 장단점이 뭔지 완벽하게 정리해서 소개하겠다.

 

1단계: 기초 개념 - JavaScript 타입의 종류
JavaScript는 원시 타입(Primitive Type)과 객체 타입(Object Type)으로 나뉜다. 원시 타입에는 number, string, boolean, undefined, symbol, bigint가 있고, 객체 타입에는 Object, Array, Function, Date, RegExp, Error 등이 있다. 여기서 중요한 건 배열도 객체, 함수도 객체라는 점이다. 그래서 typeof만 사용하면 "이건 배열인데 object라고 나온다"는 불평이 나오는 것이다.

 

2단계: typeof 연산자 - 가장 빠르지만 제한적
typeof는 가장 널리 쓰이는 타입 체킹 방식이다. 연산이 빠르고 간단해서 원시 타입을 체크할 때는 최고다. 하지만 배열, null, 함수를 구분할 때는 문제가 생긴다.

✗ 잘못된 사용법 (typeof의 함정)

let arr = [1, 2, 3];
let obj = {name: "John"};
let val = null;

console.log(typeof arr);    // "object" (배열이 object로 나옴)
console.log(typeof obj);    // "object"
console.log(typeof val);    // "object" (null도 object로 나옴)
console.log(typeof func);   // "function"

// 배열을 구분하려고 했는데 객체와 같은 결과가 나온다

✓ typeof의 올바른 사용 - 원시 타입 체크에만 사용

let num = 42;
let str = "hello";
let bool = true;
let undef = undefined;

console.log(typeof num);     // "number"
console.log(typeof str);     // "string"
console.log(typeof bool);    // "boolean"
console.log(typeof undef);   // "undefined"

// 원시 타입 체크할 때만 사용하면 정확하다

 

3단계: instanceof 연산자 - 상속 관계까지 체크
instanceof는 객체가 특정 클래스/생성자의 인스턴스인지 확인한다. 배열 여부를 체크할 때 가장 널리 쓰인다. 다만 프로토타입 체인을 따라 확인하기 때문에 iframe이나 다른 컨텍스트에서 온 객체를 체크할 때는 예상 밖의 결과가 나올 수 있다.

✗ instanceof의 함정 - 다른 컨텍스트의 배열

let mainArr = [1, 2, 3];
let iframeArr = window.frames[0].Array.from([1, 2, 3]);

console.log(mainArr instanceof Array);     // true
console.log(iframeArr instanceof Array);   // false (다른 프레임의 Array이므로)

// iframe에서 온 배열은 instanceof로 제대로 판별할 수 없다

✓ instanceof의 올바른 사용 - 같은 컨텍스트에서 객체 구분

let arr = [1, 2, 3];
let obj = {name: "John"};
let func = () => {};
let date = new Date();

console.log(arr instanceof Array);        // true
console.log(obj instanceof Array);        // false
console.log(func instanceof Function);    // true
console.log(date instanceof Date);        // true

// 배열, 함수, 날짜 객체 등을 정확히 구분할 수 있다

 

4단계: Object.prototype.toString - 가장 정확하지만 가장 느림
Object.prototype.toString().call()은 가장 정확한 타입 판별 방식이다. 내부 [[Class]] 슬롯을 직접 읽기 때문에 null, undefined, 배열, Date 등을 모두 정확히 구분한다. 다만 연산이 가장 느리다.

✓ Object.prototype.toString 사용법

let arr = [1, 2, 3];
let obj = {name: "John"};
let val = null;
let undef = undefined;
let date = new Date();
let regex = /test/;

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

console.log(getType(arr));      // "array"
console.log(getType(obj));      // "object"
console.log(getType(val));      // "null"
console.log(getType(undef));    // "undefined"
console.log(getType(date));     // "date"
console.log(getType(regex));    // "regexp"

// 모든 타입을 정확하게 구분할 수 있다

 

5단계: 세 가지 방식 비교와 실무 선택 가이드
방식속도정확도사용 사례단점
typeof매우 빠름낮음원시 타입 체크배열, null 구분 불가
instanceof빠름높음배열, 객체, 클래스 구분다른 컨텍스트에서 실패
Object.prototype.toString느림매우 높음절대적인 정확성 필요성능 오버헤드

실무 팁: 상황별 추천 조합

// 케이스 1: 배열 여부 빠르게 확인
function isArray(target) {
  return Array.isArray(target);  // ES5+ 표준 방식, 가장 빠르고 정확함
}

// 케이스 2: 원시 타입과 객체 구분
function isPrimitive(target) {
  return target !== Object(target);
  // 또는: typeof target !== 'object' && typeof target !== 'function'
}

// 케이스 3: null 안전하게 체크
function isNull(target) {
  return target === null;  // 가장 명확함
}

// 케이스 4: undefined 체크
function isUndefined(target) {
  return typeof target === 'undefined';
}

// 케이스 5: 완전한 타입 판별 (성능이 중요하지 않을 때)
function getExactType(target) {
  return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();
}

실제 라이브러리 코드 패턴 (lodash 참고)

// lodash에서는 성능을 위해 여러 방식을 조합함
function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }
  if (Object.getPrototypeOf(obj) === null) {
    return true;
  }
  return Object.getPrototypeOf(obj) === Object.prototype;
}

// 먼저 빠른 typeof로 선별하고
// instanceof나 프로토타입 체인으로 정확히 판별한다
// = 속도와 정확성의 균형

 

6단계: 주의사항 - 함정과 실수

❌ 함정 1: NaN 체크를 typeof로 하면 안 된다

let nan = NaN;

console.log(typeof nan);        // "number" (NaN도 number 타입)
console.log(nan === NaN);       // false (NaN은 자기자신과도 같지 않음)
console.log(Number.isNaN(nan)); // true (올바른 방법)

// NaN을 체크할 때는 Number.isNaN()을 사용해야 한다

❌ 함정 2: instanceof는 클래스 재할당 시 실패한다

class MyArray extends Array {}
let arr = new MyArray();

console.log(arr instanceof MyArray);  // true
console.log(arr instanceof Array);    // true

MyArray = String;  // 클래스 재할당
console.log(arr instanceof MyArray);  // false (재할당 후 실패)

// instanceof는 동적으로 할당된 클래스에는 취약하다

❌ 함정 3: Symbol 타입은 typeof만 정확히 판별한다

let sym = Symbol('test');

console.log(typeof sym);                    // "symbol" (정확함)
console.log(sym instanceof Symbol);         // false (instanceof 실패)
console.log(Object.prototype.toString.call(sym));  // "[object Symbol]"

// Symbol 타입은 typeof로만 체크하는 것이 가장 간단하다

 

7단계: 실전 예제 - 타입 검증 헬퍼 함수
// 실무에서 자주 쓰이는 타입 검증 함수 모음
const TypeCheck = {
  isArray: (val) => Array.isArray(val),
  isObject: (val) => val !== null && typeof val === 'object' && !Array.isArray(val),
  isString: (val) => typeof val === 'string',
  isNumber: (val) => typeof val === 'number' && !Number.isNaN(val),
  isBoolean: (val) => typeof val === 'boolean',
  isNull: (val) => val === null,
  isUndefined: (val) => typeof val === 'undefined',
  isEmpty: (val) => {
    if (val === null || val === undefined) return true;
    if (Array.isArray(val) || typeof val === 'string') return val.length === 0;
    if (typeof val === 'object') return Object.keys(val).length === 0;
    return false;
  },
  isDate: (val) => val instanceof Date && !Number.isNaN(val.getTime()),
  isFunction: (val) => typeof val === 'function',
  isRegExp: (val) => val instanceof RegExp,
  isPromise: (val) => val instanceof Promise || (typeof val === 'object' && typeof val.then === 'function')
};

// 사용 예제
console.log(TypeCheck.isArray([1, 2, 3]));        // true
console.log(TypeCheck.isObject({a: 1}));         // true
console.log(TypeCheck.isEmpty(''));             // true
console.log(TypeCheck.isPromise(Promise.resolve())); // true

 

핵심 정리 및 다음 단계

JavaScript 타입 체킹은 한 가지 방식만 완벽한 게 아니라 상황에 맞게 조합해서 써야 한다는 점을 기억하자. typeof는 원시 타입과 함수를 빠르게 판별할 때, instanceof는 배열과 클래스 인스턴스를 구분할 때, Object.prototype.toString은 절대적인 정확성이 필요할 때 각각 최고의 선택이다. 작은 타입 체킹 최적화가 모여서 버그 없는 견고한 코드를 만든다는 점을 잊지 말자. 이 글의 헬퍼 함수 코드를 참고해 자신의 프로젝트에 맞는 타입 검증 유틸리티를 만들면, 런타임 에러를 훨씬 줄일 수 있을 것이다.