티스토리 뷰

반응형

JS는 Python 혹은 Ruby 처럼 동적 타입 언어이다. 즉, 타입이 컴파일 시에 결정되는 것이 아닌, 런타임에서 결정되는 언어이다. 그러나, 다른 동적 언어와 달리, JS는 매우 특이한 타입 변환을 지원한다. JS의 타입 변환은 다른 언어와 다르게 기상천외한, 정말 기상천외한 방법으로 진행되니, 이에 대하여 정리가 필요할 것 같아 글을 작성하게 되었다. 

 

JS의 타입부터 살펴보고, 각 타입이 어떻게 변환되는 지 알아보자. 중간에 흐름을 놓치게 되면 이해가 되지 않는 부분이 있으니 천천히 따라와 주시길.

 

type casting
JS의 type casting에 대하여 알아보자.


1. JS의 타입

JS의 타입은 다음과 같이 7+1개가 있다. 이를 머리속에 넣고 가보자.

 

1. 원시 타입

    - 불리언 타입 (true, false)

    - Null 타입 (null)

    - Undefined 타입 (undefined)

    - Number 타입 (1, 2, 1.01, 2.34 ... )

    - BigInt 타입 (0x1ffffffffffffffn, 0b11...11n,  ...)

    - String 타입 ("hello", "world!")

    - Symbol 타입 ('foo', 'bar')

2. 객체 타입

 

이 글은 JS의 타입에 알아보는 글이 아니기에, 각각의 타입에 대한 설명은 자세히 적진 않겠다. 더 알고 싶다면 이 글을 참고하면 좋을 듯 하다.


2. JS의 타입 변환

JS는 두 가지 방법의 타입 변환이 존재한다. 첫 번째는 명시적 타입 변환으로, parseInt('100'), toString()과 같이 직접 타입을 변경해주는 방법이다. 두 번째는 암묵적 타입 변환으로, 데이터나 변수를 연산 과정에서 자동으로 타입을 강제변환하는 방법이다.

 

다만, 하나 알아두어야 할 내용은 새로운 타입으로 변환한다고 하는 것은 기존의 값을 활용해 새로운 값을 만드는 행위이다. 즉, JS 엔진은 타입 변환을 할 시 명시적 타입 변환은 타입이 변한 값을 변수에 넣고, 암묵적 타입 변환은 임시로 생성한 값으로 연산을 한 후, 버린다.

 

이번 포스팅에서는 String, Number, Boolean으로의 타입 변환을 중점적으로 설명할 예정이다.

 

2.1 String으로의 타입 변환

JS에서 어떤 객체 혹은 값을 String 타입으로 변환하는 방법은 3가지가 있다. 첫 번째 방법은 암묵적 타입 변환이고, 두번째와 세 번째는 명시적 타입 변환에 해당한다.

 

1
2
3
1. "" + value // Implicit type casting
2. value.toString() // Explicit type casting
3. String(value) // Explicit type casting
cs

 

첫 번째 방법은 value에 빈 string을 더해 value를 string 형태로 바꾸어 주는 것이고, 두 번째 방법은 toString()메서드를 활용하는 방법, 마지막 방법은 value를 함수 String()의 인자에 넣어 직접 타입을 변경해주는 방법이다.

 

1
2
3
4
5
6
7
"1" + 2 // '12'
1 + "2" // '12'
1 + "2" + 3 // '123'
true + "2" // 'true2'
undefined + "1" // 'undefined1'
null + "1" // 'null1'
123.45 + "1" // '123.451'
cs

 

첫 번째 방법을 활용 시 굳이 빈 string이 아니더라도, '+' 연산자가 피연산자의 타입을 검사해 string이 존재하면 나머지 string이 아닌 타입을 싹 다 string으로 바꾸어 버린다. 따라서, '+'로 연산을 수행할 때에는 각각의 타입이 string인지 아닌지 확인해야할 필요가 있다.

 

이 방법을 활용할 경우, 함수는 그 형태 그대로의 string을 얻을 수 있다.

 

1
2
3
4
5
6
const x = () => {
  console.log("Hello!");
}
 
+ "2"
// '() => {console.log("Hello!")}2'
cs

 

Object 혹은 Array에 사용할 경우, 예측하기 힘든 결과를 얻을 가능성이 높기에 추천하지 않는다.

 

1
2
3
4
5
6
7
8
9
const str = "2";
 
const x = {b : 2, c : 3};
const y = () => { return 0; }
const z = ['1'2];
 
+ str // '[object Object]2'
+ str // '() => {return 0;}2'
+ str // '1,22'
cs

 

두 번째 방법은 String 함수를 사용하는 것이다. 필자가 가장 추천하는 방법이기도 하고, 안전하게 어떤 타입을 string으로 바꿀 수 있는 가장 간단한 방법이라 생각한다. 다만, 이 경우도 Object, 혹은 Array를 원하는 방향으로 바꾸지 못할 가능성이 있다.

 

1
2
3
4
5
6
7
8
9
10
String(1// '1'
String(null// 'null'
String(undefined// 'null'
String('foo'// 'foo'
 
String({a : 1}) // '[object Object]'
String(function f() {return 0;}) // 'function f() {return 0;}'
String(['a''b']) // 'a,b'
String(['a', ['b']) // 'a,b'
String([{a:1}, '2', [3]]); // '[object Object],2,3'
cs

 

세 번째 방법은 toString 메서드를 사용하는 방법이다. 이 경우는 두 번째 방법과 비슷하게 활용할 수 있지만, 제한적인 상황에서만 활용할 수 있다. 아래의 예시에서 1은 바꾸지 못하지만, 1.23은 바꿀 수 있는 모습을 확인할 수 있다. (이는 소수점 인식 때문에 그렇다.)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.toString(); // Uncaught SyntaxError : Invalid or unexpected token
1.23.toString(); // '1.23'
 
"1".toString(); // '1'
 
undefined.toString(); // Uncaught TypeError: Cannot read properties of undefined (reading 'toString')
null.toString(); // Uncaught TypeError: Cannot read properties of null (reading 'toString')
 
const x = () => {return 0;};
x.toString(); // '() => {return 0;}'
 
const y = {a : 1, b : 2};
y.toString(); // '[object Object]'
 
const z = [1'2'];
z.toString(); // '1,2'
cs

 

또한, toString 메서드는 덮어쓸 수 있기에, 이에 대하여 간과해선 안된다. 이 점을 잘 활용한다면, Object 혹은 Array에 대한 string 변환을 사용자 마음 대로 바꿀 수 있다.

 

1
2
3
4
5
6
7
const newObj = {
  toString() {
    return "Hi!";
  },
};
 
newObj.toString(); // "Hi!"
cs

 

만약 Object나 Array를 그대로 string으로 변환하고 싶은 경우, JSON.stringify()를 활용하면 값들을 string으로예쁘게 바꿀 수 있다.


2.2 Number으로의 타입 변환

 

1
2
3
4
5
6
7
const one = +"2"// 2
const two = Number("2"); // 2
const three = Math.floor("2"); // 2
const four = parseInt("2"); // 2
const five = parseFloat("2"); // 2
const six = 1 * "2"// 2
const seven = ~~"2"// 2
cs

JS에서 다른 타입을 Number로 변환하는 방법은 무궁무진하게 많다. 여러 편법과 꼼수들을 모두 모아본다면, 위와 같이 7가지 방법이 있다. 두 번째, 네 번째, 다섯 번째 방법은 명시적 타입 변환에 해당하고, 그 이외의 방법은 암묵적 타입 변환에 해당한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+'1' // 1
-'1' // -1
+'1.23' // 1.23
-'4.56' // -4.56
+'' // 0
 
+true // 1
+false // 0
-true // -1
-false // -0
 
+null // 0
 
+'abc' // NaN
+'1a' // NaN
+undefined // NaN
 
const x = () => { return 0; }
const y = [1'2']
const z = {a : 1, b : 2}
+// NaN
+// NaN
+// NaN
cs

 

첫 번째 방법은 '+' 혹은 '-' 단항 연산자를 이용하는 방법이다. 이 경우 숫자 형태로 이루어진 문자열+빈 문자열, boolean, null은 변환을 할 수 있지만, 이외의 경우에는 변환에 실패하고 NaN을 반환하는 것을 알 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'1' - 1 // 0
'1' * 1 // 1
'1' / 1 // 1
'1' * -1 // -1
'1' / -1 // -1
 
'1.23' * 1 // 1.23
'1.23' / 1 // 1.23
'1.23' - 1.23 // 0
'1.23' / 1.23 // 1
 
true * 1// 1
false * 1// 0
true + 1 // 2
false / 1 // 0
 
null - 0 // 0
 
'abc' - 1 // NaN
'1a' * 1 // NaN
undefined - 1 // NaN
 
const x = () => { return 0; }
const y = [1'2']
const z = {a : 1, b : 2}
1 - x // NaN
1 - y // NaN
1 - z // NaN
cs

 

두 번째 방법은 string이 포함되지 않은 덧셈을 수행하거나, 다른 사칙연산(-, *, /) 을 활용하는 것이다. 이 경우 Number가 아닌 타입은 모두 Number로 캐스팅된다. 숫자 형태로 이루어진 문자열+빈 문자열, boolean, null은 변환될 수 있지만, 다른 형태의 경우 타입 변환에 실패하여 NaN을 반환하는 것을 알 수 있다. 또한, 0으로 나눌 경우 NaN이 반환되는 것을 알 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Math.floor('1'// 1
Math.floor('-1'// -1
Math.floor(''// 0
 
Math.floor('1.23'// 1
Math.floor('-1.23'// -2
 
Math.floor(true// 1
Math.floor(false// 0
Math.abs(true// 1
 
Math.floor(null// 0
 
Math.floor('abc'// NaN
Math.floor("1ab"// NaN
Math.floor(undefined// NaN
 
const x = () => { return 0; }
const y = [1'2']
const z = {a : 1, b : 2}
Math.floor(x) // NaN
Math.floor(y) // NaN
Math.floor(z) // NaN
cs

 

세 번째 방법은 Math 표준 내장 객체를 사용하는 것이다. 위 경우 Math.floor를 예시로 사용하였지만, 다른 메서드를 사용해도 같은 규칙을 가진다. 첫 번째 방법, 두 번째 방법과 마찬가지로 숫자 형태로 이루어진 문자열 +빈 문자열, boolean, null은 변환될 수 있지만, 다른 형태의 경우 타입 변환에 실패하여 NaN을 반환한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
parseInt('1'// 1
parseInt('-1'// -1
parseInt(''// NaN
parseInt('abc'// NaN
 
parseInt('1.23'// 1
parseInt('-2.34'// -2
parseInt('1ab'// 1
parseInt('-2bc'// -2
 
parseInt(true// NaN
parseInt(false// NaN
parseInt(null// NaN
parseInt(undefined// NaN
 
const x = () => { return 0; }
const y = [1'2']
const z = {a : 1, b : 2}
parseInt(x); // NaN
parseInt(y); // 1
parseInt(z); // NaN
 
////////////////////////////////////////////////////
 
parseFloat('1'); // 1
parseFloat('-1'); // -1
parseFloat(''// NaN
parseFloat('abc'// NaN
 
parseFloat('1.23'); // 1.23
parseFloat('-2.34'); // -2.34
parseFloat('1.2a3'); // 1.2
parseFloat('-2.045bc6'); // -2.045
 
parseFloat(true); // NaN
parseFloat(false); // NaN
parseFloat(null); // NaN
parseFloat(undefined// NaN
 
const x = () => { return 0; }
const y = [1'2']
const z = {a : 1, b : 2}
parseInt(x); // NaN
parseInt(y); // 1
parseInt(z); // NaN
cs

 

네 번째 방법과 다섯 번째 방법은 parseInt와 parseFloat 함수를 활용하는 방법이다.  함수 이름 그대로 문자열을 파싱해주는 함수로, 각각 정수와 실수형으로 파싱해주는 함수이다. 그러나 위의 규칙들과는 조금 다른 방법으로 파싱된다. parseInt와 parseFloat는 Number형인 부분까지는 정상적으로 파싱되지만, 그 이후부분부터는 되지 않는다. 또한, Array에선 Number 까진 정상적으로 파싱이 되지만, 문자열을 만난 경우 이후부터는 정상적으로 파싱이 되지 않는다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Number('1'// 1
Number('-1'// -1
Number(''// 0
Number('abc'// NaN
 
Number('1.23'// 1.23
Number('-2.34'// -2.34
Number('1ab'// NaN
Number('-2bc'// NaN
 
Number(true// 1
Number(false// 0
Number(null// 0
Number(undefined// NaN
 
const x = () => { return 0; }
const y = [1'2']
const z = {a : 1, b : 2}
Number(x); // NaN
parseInt(y); // NaN
parseInt(z); // NaN
cs

 

여섯 번째 방법은 Number 함수를 활용해 명시적으로 Number로 변환해주는 것이다. 숫자 형태로 이루어진 문자열+빈 문자열, boolean, null은 변환될 수 있지만, 다른 형태의 경우 타입 변환에 실패하여 NaN을 반환하는 것을 알 수 있다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
~~'1' // 1
~~'-1' // -1
~~'' // 0
~~'abc' // 0
 
~~('1.23'// 1
~~'-2.34' // -2
~~'1ab' // 0
~~'-2bc' // 0
~~'hello' // 0
 
~~true // 1
~~false // 0
~~null // 0
~~undefined // 0
 
const x = () => { return 0; }
const y = [1'2']
const z = {a : 1, b : 2}
~~(x); // 0
~~(y); // 0
~~(z); // 0
cs

 

일곱 번째 방법은 위의 여섯 가지 방법과 다르게, 비트 연산을 이용한 방법이다. JS는 비트 연산자 NOT에 해당하는 물결 표시(~)를 할 시 모든 비트를 '뒤집는다' (0 -> 1, 1->0). 따라서, 물결 표시를 두 번한다면 뒤집은 것을 다시 뒤집어 원본으로 복원할 수 있다. 다만 이 경우, 제한적으로 사용가능하다. 위의 경우 boolean, undefined, null, 실수가 아닌 number형은 제대로 바꾸는 모습을 볼 수 있지만, 정수가 아닌 number형은 소수점을 버림하고, 이외의 타입은 모두 0으로 처리한다.


2.3 Boolean으로의 타입 변환

 

1
2
!!value // Implicit type casting
Boolean(value) // Explicit type casting
cs

 

JS에서 다른 타입을 Boolean으로 변환하는 방법은 보통 두 가지라 할 수 있다. 하나는 Logical NOT(!)을 활용하는 방법, 다른 하나는 함수 Boolean()을 사용하는 것이다. 첫 번째의 경우 암묵적 타입 변환, 두 번째 방법의 경우 명시적 타입 변환을 활용하였다.

 

1
2
3
4
5
6
7
8
9
10
11
12
!!'' // false
!!false // false
!!null // false
!!undefined // false
!!0 // false
!!NaN // false
 
!![] // true
!!"false" // true
!!true // true
!!1 // true
!!{} // true
cs

 

먼저, Logical NOT(!)을 두 번 사용하여, true의 값을 가지는 형태는 true를, false의 값을 가지는 형태는 false를 반환하게 할 수 있다. NOT이 자동으로 해당 값을 Boolean 타입으로 변환해주므로, 이를 두 번 사용하면 원래의 값을 얻을 수 있다. 이때, 어떤 것이 true의 값을 가지는 형태이고, false의 값을 가지는 형태인지는 위에 적어놓았다. 특이하게도, 빈 Array와 Object 모두 true의 값을 가진다.

 

1
2
3
4
5
6
7
8
9
10
11
12
Boolean(''// false
Boolean(false// false
Boolean(null// false
Boolean(undefined// false
Boolean(0// false
Boolean(NaN// false
 
Boolean([]) // true
Boolean("false"// true
Boolean(true// true
Boolean(1// true
Boolean({}) // true
cs

 

두 번째 방법은, 함수 Boolean을 사용하는 것이다. 이 경우 명시적으로 변환이 가능하고, 규칙은 위 첫 번째 방법과 같은 방법을 사용한다.


이상으로 JS에서 가능한 타입 변환에 대하여 알아보았다. 이외에도 Date 타입에서 Number로, 또는 그 반대로 변환하는 방법도 있고, Array와 Object에 관한 타입 변환도 있지만, 이 경우 prototype에 관한 지식과 기타 다른 JS 지식이 필요하므로, 추후에 다른 주제를 다룰 때 잠깐잠깐 설명하는 방법으로 설명한다.

 

JS의 타입 변환은 굳이 모든 방법을 외우지 않아도 되지만, 적어도 연산자와 관련된 항목은 의도한 대로, 혹은 의도치 않게 타입이 바뀌는 경우가 자주 있으므로 JS를 다루는 개발자라면 이 정도까지는 숙지하고 가는게 좋다고 생각한다.

 

반응형
댓글
Total
Today
Yesterday
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함