본문 바로가기
Javascript

JavaScript - Type Coercion(형 변환)

by Su1993 2020. 9. 8.
반응형

자바스크립트는 예상치 못한 타입을 받았을 때, 예상 가능한 타입으로 바꿔준다. 그로 인해 사용자는 숫자 값을 넘겨야 하는 곳에 문자열을 넣을 수도 있고 문자열을 넣아야 하는 곳에 객체를 넘길 수도 있다. 이런 일이 발생했을 때 자바스크립트 엔진은 사용자가 잘못 넣은 타입을 올바른 타입으로 변환하려고 시도한다. 형 변환은 자바스크립트의 주요한 기능이지만 피해야 할 기능이기도 하다.

 

1 + "1" + 1 // 111
2 * "2" // 4

true + true // 2
1 + false // 1
2 - true // 1

const a = {
    valueOf: () => 5
}

1 + a // 6
2 * a // 10

const b = {
    toString: () => " test: "
}

b + 1 // " test: 1 "

2 * [] // 0
2 * [2] // 4
2 + [2] // "22"
2 + [1, 2] // "21, 2"
2 * [1, 2] // NaN

"string" ? 1 : 2 // 1
undefined ? 1 : 2 // 2

숫자 표현식에서 숫자가 아닌 값

문자열

숫자 문자(Numeric Characters)를 가졌다면 어떤 문자열이라도 동등한 숫자로 바뀌지만, 만약 문자열에 숫자가 아닌 것(Non-Numeric Characters)이 포함되어 있다면 NaN을 리턴하게 된다.(아래코드)

 

2 * "2" // 2 * 2
2 * Number("2") // 2 * 2
Number("3") // 3

Number("1.") // 1
Number("1.23") // 1.23
Number("0") // 0
Number("012") // 12

Number("1,") // NaN
Number("1+1") // NaN
Number("1a") // NaN
Number("ab") // NaN
Number("one") // NaN
Number("text") // NaN

 

+ 연산자

+ 연산자는 2가지의 기능을 한다.

1. 수학적인 덧셈

2. 문자열 합치기

 

문자열이 + 연산자의 피연산자로 주어졌을 때, 자바스크립트는 문자열을 숫자로 바꾸려 하지 않고 숫자를 문자로 바꾸려 한다.

 

1 + "2" // "12"
1 + "ab" // "1ab"

1 + 2 // 3
1 + 2 + 3 // 6

1 + 2 + "3" // "33"
(1 + 2) + "3" // "33"

1 + "2" + 3 // "123"
(1 + "2") + 3 // "123"

 

객체

자바스크립트에서 객체의 대부분 형 변환은 결과 값으로 [object object]를 반환한다.

 

"abc" + {} // "abc[object object]"

 

모든 자바스크립트 객체는 toString 메소드를 상속받는다. 상속받은 toString 메소드는 객체가 문자열 타입으로 변해야 할 때마다 쓰인다.

toString의 반환 값은 문자열 합치기(string concatenation) 혹은 수학적 표현식(mathematical expressions)과 같은 연산에서 쓰이게 된다.

 

const a = {}
a.toString() // [object object]

const b = {
    toString: () => "example"
}

b + " good! " // "example good!"

 

객체가 수학적 표현식 사이에 들어갔을 때 자바스크립트는 반환 값을 숫자로 변환하려 한다.

 

const a = {
    toString: () => 2
}

1 * a // 2
2 + a // 4
2 / a // 1
"text" + a // "text2"

const b = {
    toString: () => "text"
}

1 + b // "1text"
2 * b // NaN

const c = {
    toString: () => "3"
}

1 + c // "13"
2 * c // 6

 

배열 객체

배열에서 상속된 toString 메소드는 배열에서 아무런 인자도 넣지 않은 join 메소드를 호출한 것과 비슷한 방식으로 작동한다.

 

[].toString() // ""
[].join() // ""
[1, 2, 3].toString() // "1,2,3"
[1, 2, 3].join() // "1,2,3"

"a" + [1, 2, 3] // "a1,2,3"
1 + [1, 2, 3] // "11,2,3"
2 * [1, 2, 3] // NaN

2 * [] // 0
2 / [2] // 1

2 * Number([].toString()) // 0
2 * Number("") // 0
2 * 0 // 0

2 / Number([2].toString()) // 1
2 / Number("2") // 1
2 / 2 // 1

 

True, False, " "

Number(true) // 1
Number(false) // 0
Number("") // 0

1 + true // 2
1 + false // 1
1 * "" // 0
1 + "" // "1"

 

valueOf 메소드

문자열이나 숫자가 올 곳에 객체를 넘길 때마다 자바스크립트 엔진에 의해 사용될 valueOf메소드를 정의하는 것도 가능하다.

 

const a = {
    valueOf: () => 1
}

1 + a // 2
2 * a // 2

 

객체에 toString과 valueOf 메소드가 전부 정의되어 있을 때는 자바스크립트 엔진은 valueOf 메소드를 사용한다.

 

const b = {
    valueOf: () => 2,
    toString: () => 3
}

"a" + b // "a2"
2 + b // 4
2 * b // 4

 

valueOf 메소드는 객체가 어떤 숫자 값을 나타낼 때 사용하기 위해 만들어졌다.

 

const a = new Number(5)

a.valueOf() // 5

 

Truthy와 Falsy

모든 자바스크립트 값은 true 또는 false로 변환될 수 있는 특성을 갖고 있다. true로 형 변환하는 것을 truthy라 하고 false로 형변환 하는 것을 falsy라고 한다.

 

자바스크립트에서 반환 시에 falsy로 구분되는 값들은

  • 0
  • -0
  • undefined
  • null
  • false
  • NaN
  • ''
  • ""

이 외에는 전부 truthy로 구분된다.

 

if(1) // truthy
if("0") // truthy
if({}) // truthy

 

 

형 변환을 이용해도 괜찮지만, 값을 명시적으로 표현해주는 것이 좋은 작성법이다. 자바스크립트의 형변환을 아무렇지 않게 쓰게 된다면 나중에 에러 잡을 때 문제가 생길 수 있다.

 

const a = 2

if(a)
// 이렇게 쓰는 것보다

if(a === 2)
// 또는
if(typeof a === "number")

 

 

그 이유는 다음과 같은 에러가 날 수 있기 때문이다.

 

// 에러가 나는 경우
const a = (number) => {
    if(!number) new Error("error error")
}

a(0) // Error: error error

// 에러가 나지 않는 경우
const b = (number) => {
    if(typeof number !== "number") new Error("error error")
}

a(0) // 에러가 나지 않는다.

 

NaN

NaN은 자바스크립트에서 유일하게 자기 자신과 같지 않은 값이다.

 

NaN === NaN // false

const a = 1 * "a" // NaN

a == a // false
a === a // false

 

ECMAScript 6는 NaN을 체크하기 위한 메소드 Number.isNaN을 만들었다.

 

Number.isNaN(NaN) // true
Number.isNaN("a") // false

 

전역 isNaN함수는 인자가 실제로 NaN인지 체크하기 전에 인자로 들어온 값의 형 변환을 강제한다. 그러므로 전역 isNaN 함수는 사용하지 않는 것이 좋다.

 

isNaN("abc") // true
isNaN("1") // false

 

const a = (test) => {
    const b = Number(test)
    return b !== a ? true : false
}

a("1") // false
a("1ab") // true
a("ab") // true
a("NaN") // true
a(1) // false

 

 

반응형

댓글