자바스크립트 타입 변환을 예측 가능하게 다루는 법
자바스크립트의 명시적·암시적 타입 변환 규칙과 객체 변환, NaN, 비교 연산, 입력 데이터 처리 시 주의점을 정리한다.
2026-08-14 · 최초 발행 2025-08-10
런타임에 결정되는 타입과 변환
캐스팅모드(Casting Mode)는 데이터 타입을 다른 타입으로 바꾸는 방식을 말한다. 자바스크립트에서는 개발자가 직접 호출하는 명시적 타입 변환과, 엔진이 식을 평가하며 수행하는 암시적 타입 변환이 함께 작동한다.
자바스크립트는 변수 타입이 런타임에 정해지는 동적 타입 언어이며, 서로 다른 타입 사이의 자동 변환도 빈번하다. 기본 데이터 타입은 Number, String, Boolean, Null, Undefined, Symbol, BigInt의 7가지이고, Array·Function·Date 등을 포함한 Object는 참조 타입으로 다뤄진다.
실무에서 문제를 줄이려면 값 자체보다 해당 값이 어떤 문맥에서 어떤 타입으로 해석되는지를 먼저 확인해야 한다.
의도를 코드에 드러내는 명시적 변환
명시적 변환은 생성자 함수, 전역 함수, 연산자로 수행할 수 있다. 변환 지점이 코드에 남기 때문에 입력값과 외부 데이터를 다룰 때 특히 유용하다.
생성자 함수는 문자열, 숫자, 불리언 변환에 사용할 수 있다.
// 문자열로 변환
let num = 42;
let strNum = String(num); // "42"
// 숫자로 변환
let str = "42";
let numStr = Number(str); // 42
// 불리언으로 변환
let value = 0;
let boolValue = Boolean(value); // false
정수나 실수 형태로 해석할 때는 전역 함수를 사용한다.
// 정수로 변환
let floatNum = 42.9;
let intNum = parseInt(floatNum); // 42
// 실수로 변환
let str = "42.5";
let floatStr = parseFloat(str); // 42.5
짧은 연산자 표현도 타입 변환에 쓰이지만, 팀에서 읽기 쉬운 형태인지 함께 판단할 필요가 있다.
// 숫자로 변환
let str = "42";
let num = +str; // 42
// 문자열로 변환
let num = 42;
let str = num + ""; // "42"
// 불리언으로 변환
let value = 1;
let boolValue = !!value; // true
엔진이 자동으로 바꾸는 순간
암시적 타입 변환은 개발자가 변환 함수를 호출하지 않아도 연산자, 조건식, 함수 호출의 문맥에서 일어난다. 같은 값이라도 어떤 연산에 참여하느냐에 따라 문자열이나 숫자로 처리될 수 있다.
// 문자열 연결
let result = "Hello" + 42; // "Hello42"
// 산술 연산
let calc = "42" - 2; // 40 (문자열 "42"가 숫자 42로 변환됨)
// 비교 연산
let comparison = "42" > 40; // true (문자열 "42"가 숫자 42로 변환됨)
조건문과 논리 연산자는 값을 불리언 문맥에서 평가한다. 다만 논리 연산의 반환값은 항상 불리언이 아니라 평가된 피연산자일 수 있다.
// if 조건문 내에서 모든 값은 불리언으로 변환됨
if ("hello") {
console.log("This will execute because non-empty strings are truthy");
}
// 논리 연산자 사용 시
let result = "hello" && 42; // 42 (두 값 모두 truthy이므로 마지막 값 반환)
문자열 결합이 포함된 함수 호출도 암시적 변환이 발생하는 자리다.
function greet(name) {
console.log("Hello, " + name);
}
greet(42); // "Hello, 42" (숫자 42가 문자열로 변환됨)
객체와 값이 만나는 지점
객체를 문자열이나 숫자 같은 원시 타입으로 바꿀 때는 toString과 valueOf가 변환 결과에 관여할 수 있다.
// 객체를 문자열로 변환
let obj = {
toString: function () {
return "Custom String";
},
};
console.log(String(obj)); // "Custom String"
// 객체를 숫자로 변환
let obj = {
valueOf: function () {
return 42;
},
};
console.log(Number(obj)); // 42
빈 배열, null, undefined처럼 일상적인 직관과 다른 결과를 내는 값도 있다. 이런 사례는 느슨한 비교나 자동 변환에 의존할수록 오류의 원인이 되기 쉽다.
// 빈 배열의 변환
console.log(Number([])); // 0
console.log(String([])); // ""
console.log(Boolean([])); // true
// null과 undefined의 변환
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
// 논리 연산자의 결과
console.log("" || "default"); // "default"
console.log("value" && "result"); // "result"
반복 경로에서는 변환 위치를 분리한다
타입 변환은 성능에도 영향을 줄 수 있다. 대량 데이터를 반복 처리하는 경로에서는 반복문 안의 변환을 줄이고, 같은 변환 결과가 계속 필요하다면 재사용하는 편이 낫다. 처음부터 목적에 맞는 데이터 타입을 선택하는 것도 변환 비용을 낮추는 방법이다.
// 비효율적인 방식
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += "1"; // 매 반복마다 문자열이 숫자로 변환됨
}
// 효율적인 방식
let sum = 0;
let one = 1; // 미리 숫자로 변환
for (let i = 0; i < 1000000; i++) {
sum += one;
}
입력값과 외부 데이터를 변환하는 자리
웹 애플리케이션의 사용자 입력은 기본적으로 문자열이다. 숫자가 필요한 필드라면 변환 결과가 유효한지 확인한 뒤 다음 처리로 넘겨야 한다.
// HTML: <input type="text" id="age" />
const ageInput = document.getElementById("age");
const userAge = Number(ageInput.value);
if (isNaN(userAge)) {
alert("유효한 나이를 입력해주세요.");
} else {
// 유효한 숫자로 처리
processUserAge(userAge);
}
API 응답도 애플리케이션 내부에서 기대하는 타입과 다를 수 있다. 숫자 문자열을 정렬이나 계산에 사용하기 전에 숫자로 바꾸는 식으로 경계를 명확히 둔다.
// API 응답에서 숫자 문자열을 실제 숫자로 변환
fetch("https://api.example.com/products")
.then((response) => response.json())
.then((data) => {
// 가격 문자열을 숫자로 변환
const products = data.map((product) => ({
...product,
price: Number(product.price),
}));
// 이제 가격으로 정렬 가능
products.sort((a, b) => a.price - b.price);
});
표시 형식을 만들 때도 값의 타입을 먼저 정리하면 포맷 함수의 의도가 명확해진다.
// 숫자를 통화 형식으로 변환
function formatCurrency(value) {
return "$" + Number(value).toFixed(2);
}
// 날짜 객체를 사용자 친화적 문자열로 변환
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString("ko-KR", {
year: "numeric",
month: "long",
day: "numeric",
});
}
비교와 NaN에서 자주 생기는 오해
느슨한 동등 연산자 ==는 비교 과정에서 타입 변환을 수행한다. 값과 타입을 함께 비교하는 ===, !==를 사용하면 이 변환에 기대지 않아도 된다.
// 예상치 못한 결과
console.log("0" == 0); // true
console.log([] == 0); // true
console.log("" == 0); // true
// 해결책: 엄격한 동등 연산자(===) 사용
console.log("0" === 0); // false
console.log([] === 0); // false
console.log("" === 0); // false
NaN은 자기 자신과도 같지 않으므로 비교 연산으로 판별할 수 없다. 숫자 변환 결과를 검사할 때는 isNaN 또는 Number.isNaN을 사용한다.
// 문제
let result = parseInt("Not a Number");
console.log(result); // NaN
console.log(result == NaN); // false! NaN은 자기 자신과도 같지 않음
// 해결책
console.log(isNaN(result)); // true
// 더 정확한 방법
console.log(Number.isNaN(result)); // true
배열 인덱스처럼 문자열이 허용되는 자리도 있지만, 의도한 인덱스임을 드러내려면 명시적으로 변환하는 편이 낫다.
// 문제
let arr = [1, 2, 3];
let idx = "2";
console.log(arr[idx]); // 3 (문자열 인덱스가 숫자로 변환됨)
// 명시적 변환으로 의도 명확히 하기
let safeIdx = Number(idx);
console.log(arr[safeIdx]); // 3
타입스크립트가 막아 주는 경계
타입스크립트는 자바스크립트의 상위 집합 언어로 정적 타입 시스템을 제공한다. 타입 불일치의 일부를 컴파일 시간에 확인할 수 있지만, 외부 입력과 런타임 값의 실제 변환까지 사라지는 것은 아니다.
// 타입스크립트에서 명시적 타입 지정
let age: number = 30;
let name: string = "John";
// 오류 발생 (컴파일 시간에 감지)
age = "thirty"; // Type 'string' is not assignable to type 'number'
// 명시적 타입 변환 (타입 어서션)
let value: any = "42";
let numValue: number = value as number;
// 또는
let numValue: number = <number>value;
변환이 반복되는 코드라면 유틸리티 함수로 경계를 모아 둘 수 있다. 명시적 변환을 우선하고, 사용자 입력과 외부 데이터는 유효성을 검사하며, 가능한 경우 타입스크립트의 정적 검사를 함께 활용한다.
// 타입 안전 유틸리티 함수 예시
function safeParseInt(value, defaultValue = 0) {
const parsed = parseInt(value);
return isNaN(parsed) ? defaultValue : parsed;
}
// 사용 예
const userCount = safeParseInt(userCountInput.value, 1);