개발하다 보면 변수가 정확히 어떤 타입인지 확인해야 할 순간이 자주 온다. 다만 대부분의 개발자들은 typeof 하나만 쓰다가 예상 못 한 결과에 당황하곤 한다. 특히 배열이나 null을 체크할 때 typeof가 제대로 된 결과를 주지 않는다는 걸 모르는 경우가 많다. 이번에는 JavaScript의 세 가지 타입 체킹 방식을 정확히 뭔지, 언제 써야 하는지, 각각의 장단점이 뭔지 완벽하게 정리해서 소개하겠다.
✗ 잘못된 사용법 (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"
// 원시 타입 체크할 때만 사용하면 정확하다
✗ 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
// 배열, 함수, 날짜 객체 등을 정확히 구분할 수 있다
✓ 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"
// 모든 타입을 정확하게 구분할 수 있다
| 방식 | 속도 | 정확도 | 사용 사례 | 단점 |
|---|---|---|---|---|
| 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나 프로토타입 체인으로 정확히 판별한다
// = 속도와 정확성의 균형
❌ 함정 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로만 체크하는 것이 가장 간단하다
// 실무에서 자주 쓰이는 타입 검증 함수 모음
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은 절대적인 정확성이 필요할 때 각각 최고의 선택이다. 작은 타입 체킹 최적화가 모여서 버그 없는 견고한 코드를 만든다는 점을 잊지 말자. 이 글의 헬퍼 함수 코드를 참고해 자신의 프로젝트에 맞는 타입 검증 유틸리티를 만들면, 런타임 에러를 훨씬 줄일 수 있을 것이다.