자바스크립트 타입 변환

notion image
자바스크립트는 변수의 타입에 할당된 값에 따라 변하기 때문에 동적 타입 언어라고 불립니다.
동적 타입 언어의 특징으로 서로 다른 타입의 값을 처리해야 하는 경우도 자주 발생합니다. 다른 언어는 타입이 맞지 않는 경우 타입 에러를 발생하기도 하지만, 자바스크립트는 타입 강제 변환으로 타입을 맞춰 처리합니다.
개발자의 의도와 상관없이 자동으로 일어나는 타입 변환을 암시적 타입 변환이라고 합니다. 하지만 개발자가 타입 변환 함수 등을 사용해 의도적으로 타입을 변환하는 것은 명시적 타입 변환이라고 합니다.
타입 강제 변환을 통해 피연산자가 어떤 타입으로 변환될 것인지는 연산자에 따라 다릅니다. + 연산자는 숫자를 합하기도 하지만, 문자열을 연결하는 연산자이기도 합니다. 반면 - 연산자는 숫자만 받아 처리할 수 있습니다.
Null, Undefined 그리고 Symbol 타입을 제외한 모든 타입에 해당 타입으로 변환되는 규칙이 존재합니다.

Number 타입 변환

타입
변환 규칙
Null
0으로 변환
Undefined
NaN으로 변환
Boolean
true는 1로, false는 0으로 변환
String
Number 리터럴로 파싱되면 숫자로 변환, 아니면 NaN으로 변환
BigInt
암시적 변환의 경우 정확성을 잃을 수 있기 때문에 타입 에러(단, Number 함수를 통해 명시적으로 변환되는 경우는 정확성을 잃더라도 숫자로 변환)
Symbol
타입 에러

Object 타입의 Number 타입으로의 변환

Object 타입을 Number 타입으로 변환할 때의 규칙은 다른 원시 타입에 비해 복잡합니다.
 
1) Symbol.toPrimitive
Object 타임의 Symbol.toPrimitive메서드는 객체를 원시값으로 변환해주는 메서드입니다. 첫 번째 인수로 number, string과 같은 변환할 타입에 대한 타입 힌트를 문자열로 전달할 수 있습니다. 만약 + 연산자와 같이 숫자와 문자열을 모두 받을 수 있다면 default 값을 전달합니다.
luckyNumber 객체는 number, string, default 값을 가질 수 있는 타입 힌트 인자를 받아 원시값을 반환하는 Symbol.toPrimitive 메서드를 정의하고 있습니다. 해당 예시처럼 기본적으로 Symbol.toPrimitive 존재하지 않습니다. 그렇기 때문에 암시적 타입 변화를 할 때, Symbol.toPrimitive 메서드가 있다면 해당 동작을 진행합니다.
 
2) valueOf
기본적으로 객체 자기 자신인 this를 반환하지만, 타입 변환을 위해 원시값을 반환하는 메서드로 재정의할 수 있습니다. Symbol.toPrimitive메서드와 달리 아무 인자도 받지 않으며 반환값이 꼭 원시값일 필요도 없습니다.
 
3) toString
toString 메서드는 이름 그대로 객체를 문자열로 변환하기 위한 메서드입니다.
세 가지 메서드는 타입 강제 변환 시 자바스크립트에 의해 자동으로 호출됩니다. 모두 정의되어 있을 필요는 없지만, 적어도 하나는 정의되어 있어야 에러가 발생하지 않습니다.
숫자 타입으로 변환할 때도 세 가지 메서드가 Symbol.toPrimitive(타입 힌트 : number), valueOf, toString 순으로 원시값을 반환받을 때까지 호출됩니다.
예시에서 NaN이 나오는 과정을 한번 알아보자면,
  1. +obj는 숫자로 바꾸려고 합니다. → toPrimitive 호출
  1. toPrimitive가 없기 때문에 valueOf를 호출 → valueOf는 객체 자신을 반환
  1. valueOf의 결과가 원시값이 아니므로 toString 호출 → “[object Object]”(문자열, 원시값)
  1. Number("[object Object]")시도 → 숫자로 해석 불가 → NaN

단항 + 연산자

숫자 타입 중에서 Number 타입으로만 강제 변환을 하는 대표적 연산자 중 하나입니다. 단항 - 연산자와 달리 단항 + 연산자는 BigInt 타입에 대해 타입 에러를 발생 시킵니다.

비트 연산과 TypedArray

자바스크립트의 숫자 타입은 표면적으로는 NumberBigInt가 있지만, 내부적으로 이진 데이터를 저장하기 위해서 고정 너비 정수 타입도 존재합니다. 생성 함수나 리터럴이 존재하지 않고, 비트 연산을 통한 타입 변환이나 TypedArray라는 특수 배열 객체를 통해 다룰 수 있습니다.
비트 연산은 Number 타입인 경우 32비트의 고정 너비 정수로 변환합니다. 즉, 32비트 이상의 정수이거나 소수점을 가지고 있는 경우 소실됩니다.
TypedArray는 고정 길이의 해석할 방법이 없는 데이터 덩어리이진 버퍼(ArrayBuffer)를 정수·부동소수·BigInt 등 원시 타입으로 보여주고 빠르게 이진 데이터 처리를 가능하게 해주는 배열입니다.
일반 Array도 있는데 굳이 TypedArray를 사용하는 이유는,
  • 고정 타입 & 고정 크기 : 요소당 바이트 수가 고정이라 JIT 최적화와 메모리 접근이 효율적입니다. → Array는 요소의 타입이 자유롭고 길이와 구조가 바뀌기 쉽지만 TypedArray는 타입과 크기가 고정입니다.
  • 경계 검사 포함 : 범위를 넘어선 접근은 예외가 발생합니다.
  • Zero-copy 뷰 : 같은 버퍼를 여러 뷰로 재활용합니다. → 불필요한 복사가 발생하지 않습니다.
  • 네이티브/브라우저 API와 호환 : WebGL, Canvas, File/Blob, Streams, Socket 등

String 타입 변환

타입
변환 규칙
Null
“null”로 변환
Undefined
“undefined”로 변환
Boolean
true는 “true”로, false는 “false”으로 변환
Number
10진수 형식의 문자열로 변환
BigInt
10진수 형식의 문자열로 변환
Symbol
타입 에러

Object 타입의 String 타입으로의 변환

Object 타입을 String 타입으로 변환할 때도 Number 타입에서설명한 세가지 메서드가 순서대로 사용됩니다. 차이점은 대체로 Symbol.toPrimitive(타입 힌트 “string”), toString, valueOf 순서로 호출됩니다.

문자열 연결

String 타입으로 강제 변환 일으키는 대표적인 연산은 문자열 연결입니다. 문자열을 연결하는 방법으로 + 연산자를 사용하는 방법, concat메서드를 사용하는 방법, 템플릿 리터럴을 사용하는 방법 등이 있지만 약간의 차이점이 있습니다.
+ 연산자는 숫자와 문자 모두 받을 수 있기 때문에 Symbol.toPrimitive(타입 힌트 “default”), valueOf, toString 메서드를 호출합니다.
하지만 concat또는 템플릿 리터럴을 사용하는 경우 Symbol.toPrimitive(타입 힌트 “string”), toString, valueOf메서드를 차례로 호출합니다.
그래서 문자열을 연결하는 경우 + 연산자보다는 concat과 템플릿 리터럴을 사용하는 편이 의도치 않는 버그를 줄일 수 있습니다.

Boolean 타입 변환

타입
변환 규칙
Null
false로 변환
Undefined
false로 변환
Number
0, -0, NaN은 false, 나머지는 true
BigInt
0n은 false로 변환, 나머지 숫자는 true
String
빈 문자열은 false, 나머지는 true
Symbol
true로 변환
Object
true로 변환
Boolean 타입으로 강제 변환 시키는 방법은 이중 NOT 연산자(!!)와 Boolean 함수, if문 등이 있습니다.

객체 타입 변환

객체 타입을 피연산자로 기대하는 연산자는 원시 타입을 객체 타입으로 강제 변환합니다. NullUndefined을 제외한 모든 원시 타입은 래퍼 객체가 존재합니다. 해당 래퍼 객체로 원시값을 감싸게 됩니다. UndefinedNull은 래퍼 객체가 없으므로 타입 변환 시도 시 에러가 발생합니다.
객체 타입으로 강제 변환을 일으키는 방법에는 Object.prototype.valueOf메서드나 Object 함수 등이 있습니다.
Object 함수도 비슷하게 원시값을 객체로 변환해주지만, undefinednull값에 대해 에러를 발생시키지 않고 빈 객체를 반환합니다.