4. 명시적 변환, 암시적 변환, Nominal, 구조화, 덕 타이핑

Park.Dyel·2020년 5월 2일
0

FE

목록 보기
1/6

변환

시작하기에 앞서, 1,2,3 단원은 잘못된 마음 먹음으로 날려버렸다.

동적 타입의 특성을 가지는 JavaScript는 변수의 형태를 코드에서 지정하지 않고 코드가 실행될 때 변수의 형태도 해석된다.

이러한 변수의 형태를 해석하는 것에 있어 강제적으로 형변환이 일어나, 방어적으로 코딩을 하지 않으면 많은 문제가 생길 수 있는데 이 과정에 대해 살펴보도록 한다.

비-숫자형 형태의 숫자 형태로의 변환

문자형

숫자형 데이터의 -, *, %, /와 같은 연산의 피연산자로 문자열을 전달하면 빌트인 함수인 Number 와 유사하게 변환한다. 이는 꽤나 단순하고 숫자형 문자는 그 값대로 변환된다. 다만, 문자열에 숫자형 문자이외의 문자가 있는 경우 NaN 을 반환한다.

3 * "3"		// 9
3 * Number("3")	// 9
Number("1.")	// 1
Number("0012")	// 12
Number("1.34")	// 1.34

Number("1,")	// NaN
Number("1+1")	// NaN
Number("1a")	// NaN
Numver("one")	// one

+ 연산자

+ 연산자의 경우, 다른 수학적 연산자와는 다르게 동작하는 데, 아래의 두 기능을 수행한다.

  • 수학적 가산
  • 문자열 연결

문자열 데이터가 + 연산의 연산자인 경우 문자열을 숫자형으로 변환하는 것이 아니라, 숫자형을 문자열로 변환한다.

1+"2"		// "12"
"1"+2		// "12"
1+"2js"		// "12js"

1+2+"1"		// 31
(1+"2")+1	// 121

객체형

대부분의 JavaScript 객체는 [object Object]로 변한된다.

name + {}	// "name[object Object]"

다만 모든 JavaScript 객체는 어쨋든 객체를 문자열로 변환해주는 toString 메소드를 상속받는다.

const foo = {}
foo.toString()		// "[object Object]"

const baz = {
  toString:
  	() => "run toString"
}
baz.toString() + !	// "run toString!"

배열형

배열형에 상속된 toString은 약간 다르게 동작한다. 인자가 없는 join과 비슷하게 동작한다.

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

"me" + [1,2,3]		// "me1,2,3"
4 + [1,2,3]		// 41,2,3
4 * [1,2,3]		// NaN

그래서 문자열 형태의 반환값이 기대되는 실행에 배열을 전달하면, 두 번째 피연산자로 toString의 반환 값을 연결한다. 만약 숫자형이 기대되는 실행에 전달하면 반환 값을 숫자형으로 변환하려고 시도한다.

So when you pass an array where it expects a string, Javascript concatenates the return value of the toString method with the second operand. If it expects a number, it attempts to convert the return value to a number

4 * []	// 4
4 / [2]	// 2

4 * Number([])			// 0
4 * Number([].toSring())	// 0
4 * Number("")			// 0
4 * 0				// 0

valueOf 메소드

객체가 숫자형이나 문자열로 예상되는 곳에 넘겨질 때 사용하기 위해 valueOf 를 정의할 수 있다.

const foo = {
  valueOf: () => 3
}

3 + foo		// 6
3 * foo		// 9

객체에 toStringvalueOf 메소드가 함께 정의되어 있는 경우, JavaScript는 valueOf 메소드를 대신 사용한다.

const bar = {
  toString:() => 2,
  valueOf:() => 5
}

"sa" + bar		// "sa5"
3 * bar 		// 15
2 + bar			// 7

valueOf 메소드는 객체를 숫자형 값으로 표현하는 것을 지원한다.

const two = new Number(2)
two.valueOf()			// 2

Falsy and Truthy

모든 JavaScript 값은 true나 false로 강제 변환(coerced) 될 수 있다. 아래의 몇 안되는 값들은 falsy 값을 반환한다.

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

나머지 모든 값은 true이다.

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

위의 사실들로 충분하지만, 참인지 거짓인지 확실하기 위해선 명확하게 하는것이 좋다. 예를 들어, 아래의 코드 A 대신 코드 B를 사용하는 것이 낫다.

// Code A
const counter = 2;
if (counter)
  
// Code B-1
if (counter === 2)
// Code B-2
if (typeof counter === "number")

예를 들어, 함수를 숫자형으로만 동작하게 정의했다면, 이러한 검사를 통해 의도지 않은 동작을 방지할 수 있다.

// Uhm.....
const add = (number) => {
  if(!number)
    new Error("Only accepts arguments of type: number")
  // code
}

add(0)		// Error!

// Better Good!!
const add = (number) => {
  if (typeof number !== "number")
    new Error("Only accepts arguments of type: number")
  //number
}

add(0)		// no Error

NaN

NaN은 자신과 같지 않은 특별한 값이다.

NaN === NaN 	// false

const notANumber = 3 * "a"	// NaN
notANumber == notANumber	// false
notANumber === notANumber	// false

NaN 은 JavaScript 내에서 유일하게 그 자신과 같지 않은 값이다. 그래서 검사하기 위해선 자기 자신과 비교해야 한다.

if (notANumber !== notANumber) // true

ECMAScript 6에서는 NaN을 확인하기 위해 Number.isNaN이 제공된다.

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

Global NaN의 경우 확인하기 전해 형을 강제로 변환하는 것에 주의해야 한다.

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

Global NaN은 피하는 것이 낫다. 이와 같이 동작하기 위해선 아래와 같이 작성하면 된다.

const coerceThenCheckNaN = (val) => {
  const coercedVal = Number(val)
  return coercedVal !== coercedVal ? true : false
}

coerceThenCheckNaN("1a") // true
coerceThenCheckNaN("1") // false
coerceThenCheckNaN("as") // true
coerceThenCheckNaN(NaN) // true
coerceThenCheckNaN(10) // false
profile
ㄱH발자

0개의 댓글