[Javascript] 타입 변환 / 강제 변환
JS type coercion, JS 타입 변환, JS 형변환, implicit conversion
정의
JavaScript 의 암묵적 형변환 (coercion). 연산자가 양쪽 피연산자의 타입을 자동으로 맞춘다. 명시적 변환과 구분.
1 + '2' // '12' (implicit, number → string)
'5' * 2 // 10 (implicit, string → number)
Number('5') // 5 (explicit)
3 가지 강제 변환
1. ToString
String(123) // '123'
String(true) // 'true'
String(null) // 'null'
String(undefined) // 'undefined'
String([1,2,3]) // '1,2,3'
String({}) // '[object Object]'
String({a: 1}) // '[object Object]'
String(Symbol('x')) // 'Symbol(x)'
// 암묵적
123 + '' // '123'
`${123}` // '123'
2. ToNumber
Number('42') // 42
Number('42px') // NaN (엄격)
Number('') // 0 ⚠️
Number(' ') // 0 ⚠️
Number(true) // 1
Number(false) // 0
Number(null) // 0 ⚠️
Number(undefined) // NaN
Number([]) // 0 ⚠️
Number([1]) // 1 ⚠️
Number([1, 2]) // NaN
Number({}) // NaN
// 암묵적
+'42' // 42
'42' * 1 // 42
'42' - 0 // 42
parseInt / parseFloat 는 관대 (앞쪽 숫자만 추출).
parseInt('42px') // 42
Number('42px') // NaN
3. ToBoolean
Boolean(0) // false
Boolean('') // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
Boolean(0n) // false
// 나머지 truthy
// 암묵적
if (x) { ... }
!!x
x && y
== 의 강제 변환 규칙
'5' == 5 // true
null == undefined // true
null == 0 // false (특수)
'' == 0 // true
'' == false // true
0 == false // true
[] == false // true (배열 → 빈 문자열 → 0)
[] == 0 // true
[0] == false // true
[1] == true // true
[1] == 1 // true
[1, 2] == '1,2' // true
규칙이 너무 복잡 → 항상 === 사용 이 정답.
+ 연산자의 양면성
1 + 2 // 3 (둘 다 number)
1 + '2' // '12' (하나라도 string → string)
'1' + 2 // '12'
{} + [] // 0 (parser 가 {} 를 block 으로 해석) 또는 '[object Object]'
[] + {} // '[object Object]'
[] + [] // '' (둘 다 toString → '')
+ 는 양쪽 중 하나가 문자열이면 concat, 둘 다 number 면 합.
다른 연산자
'5' - 3 // 2 (둘 다 number 로 변환)
'5' * 3 // 15
'5' / 0 // Infinity
'a' - 1 // NaN
'5' > 3 // true (둘 다 number)
'5' > '3' // true (둘 다 string, 사전순)
'10' > '9' // false ('1' < '9' 사전순)
객체의 변환
const obj = {
valueOf() { return 42; },
toString() { return 'forty-two'; }
};
obj + 1 // 43 (valueOf 우선)
`${obj}` // 'forty-two' (toString 우선, template)
String(obj) // 'forty-two'
Number(obj) // 42
Symbol.toPrimitive 로 명시적 제어 가능.
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') return 42;
if (hint === 'string') return 'forty-two';
return 'default';
}
};
함정
1. 빈 배열의 변환
[] == false // true (헷갈림)
![] == false // true (헷갈림 × 2)
2. null 의 비교
null > 0 // false
null < 1 // true
null == 0 // false (모순)
null >= 0 // true
null == undefined 만 true, 다른 비교는 모두 number 변환 후 비교.
3. NaN
NaN == NaN // false (유일하게 자기 자신과 다름)
[NaN].includes(NaN) // true (특수)
Number.isNaN(x) // 명시적 검사
4. JSON 의 변환
JSON.stringify({ a: undefined }) // '{}' (undefined 제거)
JSON.stringify({ a: null }) // '{"a":null}'
JSON.stringify({ a: NaN }) // '{"a":null}' (NaN → null)
JSON.stringify({ a: Infinity }) // '{"a":null}'
모범 사례
- 항상
===/!== - 명시적 변환:
Number(),String(),Boolean() x == null만 예외적 허용 (null 또는 undefined 검사)??(nullish coalescing) 활용
타입 변환 흐름도
hint 에 따라 Symbol.toPrimitive → toString / valueOf 순서가 결정됩니다.
flowchart TD
TRIG["강제 변환 발생"] --> SH["hint: string"]
TRIG --> NH["hint: number"]
TRIG --> DH["hint: default"]
SH --> SP1["Symbol.toPrimitive 있으면 사용"]
SH --> NO1["없으면 toString, 실패 시 valueOf"]
NH --> SP2["Symbol.toPrimitive 있으면 사용"]
NH --> NO2["없으면 valueOf, 실패 시 toString"]
DH --> SP2
SP1 --> RES["원시값 완성"]
NO1 --> RES
SP2 --> RES
NO2 --> RES
연산자별 변환 요약
flowchart LR
PLUS["+ 연산자"] -->|"둘 다 number"| NUM["숫자 덧셈"]
PLUS -->|"하나라도 string"| STR["문자열 연결"]
ARITH["산술 연산자"] -->|"항상 ToNumber"| TONUM["숫자 연산"]
CMP["비교 연산자"] -->|"둘 다 string"| LEXI["문자열 사전 비교"]
CMP -->|"나머지"| TONUM2["ToNumber 후 비교"]
Object.is 비교
=== 의 두 가지 예외를 올바르게 처리합니다.
| 비교 | == | === | Object.is |
|---|---|---|---|
1 == '1' | true | false | false |
null == undefined | true | false | false |
NaN 자기 비교 | false | false | true |
+0 vs -0 | true | true | false |
Object.is(NaN, NaN) // true (NaN 검사에 사용)
Object.is(+0, -0) // false (부호 0 구분 필요 시)
// React useState 내부도 Object.is 사용
// Redux 는 Object.is 기반 얕은 비교
Object.is(NaN, NaN) 이 true 이므로 Number.isNaN 대신 사용 가능.
TypeScript 에서의 암묵적 변환
TypeScript 타입 시스템은 컴파일 타임에 강제 변환 오류를 잡아 주지만, as 단언은 런타임 변환이 아닙니다.
// 잘못된 as 사용
const raw = '5' as unknown as number;
const result = raw + 1;
// 런타임: '51' (string concat!) - TypeScript 는 number 로 보지만 값은 여전히 string
// 올바른 명시적 변환
function toNumber(v: unknown): number {
const n = Number(v);
if (Number.isNaN(n)) throw new TypeError(`변환 불가: ${String(v)}`);
return n;
}
// 타입 가드 + 변환 조합
function coerceToString(v: string | null | undefined): string {
return v ?? ''; // null / undefined 만 빈 문자열로
}
WARNING
as unknown as T 는 TypeScript 타입 검사기를 우회합니다. 런타임 값이 실제로 T 임을 보장하지 않습니다.
실전 패턴
// 1. 숫자 변환 3가지 방법
+'42' // 42, unary +
Number('42') // 42, 명시적
parseInt('42px', 10) // 42, 관대 파싱 (기수 반드시 명시)
// 2. nullish vs falsy 구분
const a = userInput ?? 'default'; // null / undefined 만 처리
const b = userInput || 'default'; // falsy 전체 (빈 문자열도 대체!)
// 3. 안전한 숫자 변환
const n = Number(raw);
const safe = Number.isFinite(n) ? n : 0;
// 4. 배열의 암묵적 변환 방지
const display = Array.isArray(v) ? v.join(', ') : String(v);
// 5. 불리언 변환: !! vs Boolean()
!!0 // false
Boolean(0) // false (동일, Boolean() 가 더 명시적)
!!'0' // true (문자열 '0' 은 truthy!)
!![] // true (빈 배열도 truthy!)
참고
이 글의 용어 (6개)
- [Javascript] boolean / null / undefinedjavascript
- 정의 JavaScript 의 3 가지 primitive. - : , - : 의도적인 "없음" - : 자동으로 부여된 "없음" (초기화 안 됨) 사용 상황 | 상황 | 권장 값 /…
- [Javascript] JSONjavascript
- 정의 JSON (JavaScript Object Notation) 은 데이터 교환 형식. JS 의 객체 리터럴과 비슷하지만 엄격한 문법. - : JS 값 → JSON 문자열 - …
- [Javascript] NaN / Infinityjavascript
- 정의 - (Not a Number) : 의미 없는 수치 연산의 결과 - , : 너무 큰/작은 수, 0 으로 나누기 IEEE 754 표준에 따른 특수 값. 타입이지만 특이한 동작.…
- [Javascript] Numberjavascript
- 정의 JavaScript 의 는 IEEE 754 double-precision float (64-bit). 정수와 실수를 같은 타입으로 표현. 정수 안전 범위는 . IEEE 75…
- [Javascript] Stringjavascript
- 정의 JavaScript 의 은 UTF-16 코드 유닛의 불변 시퀀스. 작은따옴표, 큰따옴표, 백틱 (template literal) 으로 작성. 사용 상황 | 상황 | 권장 패…
- [Javascript] Symboljavascript
- 정의 은 ES6 도입의 7번째 primitive 타입. 유일성 을 보장하는 식별자. 왜 필요한가 객체의 충돌 없는 키 로 사용. private property 흉내, well-k…
💬 댓글