오늘은 모던 자바스크립트 튜토리얼 객체의 객체를 원시형으로 변환하기에 대해 공부하겠습니다.
obj1 + obj2 처럼 객체끼리 더하는 연산을 하거나, obj1 - obj2 처럼 빼는 연산을 하면 어떤 일이 일어날까? alert(obj)로 출력하면 무슨 일이 발생할까?
이 모든 경우에 자동 형 변환이 일어난다. 객체는 원시값으로 변환되고, 그 후 의도한 연산이 수행된다.
형 변환 챕터에선 객체의 형 변환은 다루지 않았다. 원시형 자료가 어떻게 문자, 숫자, 논리형으로 변환되는지만 알아보았었다. 이젠 메서드와 심볼에 대한 지식을 갖추었으니 본격적으로 이 공백을 메꿔보자.
객체는 논리 평가 시 true를 반환한다. 단 하나의 예외도 없다. 따라서 객체는 숫자형이나 문자형으로만 형 변환이 일어난다고 생각하면 된다.
숫자형으로의 형 변환은 객체끼리 빼는 연산을 할 때나 수학 관련 함수를 적용할 때 일어난다. 객체 Date끼리 차감하면 (date1 - date2) 두 날짜의 시간 차이가 반환된다.
문자형으로의 형 변환은 대개 alert(obj) 같이 객체를 출력하려고 할 때 일어난다.
객체 형 변환은 세 종류로 구분되는데, 'hint'라 불리는 값이 구분 기준이 된다. 'hint'가 무엇인지는 명세서에 자세히 설명되어 있는데, '목표로 하는 자료형' 정도로 이해하면 된다.
"string"
alert 함수같이 문자열을 기대하는 연산을 수행할 때는(객체-문자형 변환), hint가 string이 된다.
"number"
수학 연산을 적용하려 할 때(객체-숫자형 변환), hint는 number가 된다.
"default"
연산자가 기대하는 자료형이 '확실치 않을 때' hint는 default가 된다. 아주 드물게 발생한다.
이항 덧셈 연산자 +는 피연산자의 자료형에 따라 문자열을 합치는 연산을 할 수도 있고 숫자를 더해주는 연산을 할 수도 있다. 따라서 +의 인수가 객체일때는 hint가 default가 된다.
동등 연산자 ==를 사용해 객체-문자형, 객체-숫자형, 객체-심볼형끼리 비교할 때도, 객체를 어떤 자료형으로 바꿔야 할지 확신이 안 서므로 hint는 default가 된다.
크고 작음을 비교할 때 쓰이는 연산자 <, > 역시 피연산자에 문자형과 숫자형 둘 다를 허용하는데, 이 연산자들은 hint를 'number'로 고정한다. hint가 'default'가 되는 일이 없다. 이는 하위 호환성 때문에 정해진 규칙이다.
실제 일을 할 때는 이런 사항을 모두 외울 필요는 없다. Date 객체를 제외한 모든 내장 객체는 hint가 "default"인 겨우와 "number"인 경우를 동일하게 처리하기 때문이다. 우리도 커스텀 객체를 만들 땐 이런 규칙을 따르면 된다.
'boolean' hint는 존재하지 않는다. 모든 객체는 그냥 true로 평가된다. 게다가 우리도 내장 객체에 사용되는 규칙처럼 "default"와 "number"를 동일하게 처리하면, 결국엔 두 종류의 형 변환(객체-문자형, 객체-숫자형)만 남게 된다.
실제 돌아가는 예시를 살펴보자. user 객체에 객체-원시형 변환 메서드
obj[Symbol.toPrimitive].(hint)를 구현해보자.
이렇게 메서드를 구현해 놓으면 user는 hint에 따라 (자기 자신을 설명해주는) 문자열로 변환되기도 하고 (가지고 있는 돈의 액수를 나타내는) 숫자로 변환되기도 한다.
user[Symbol.toPrimitive]를 사용하면 메서드 하나로 모든 종류의 형 변환을 다룰 수 있다.
객체에 Symbol.toPrimitive가 없으면 자바스크립트는 아래 규칙에 따라 toString이나 valueOf를 호출한다.
이 메서드들은 반드시 원시값을 반환해야한다. toString이나 valueOf가 객체를 반환하면 그 결과는 무시된다. 마치 메서드가 처음부터 없었던 것처럼 되어버린다.
일반 객체는 기본적으로 toString과 valueOf에 적용되는 다름 규칙을 따른다.
데모를 보자.
이런 이유 때문에 alert에 객체를 넘기면 [object Object]가 출력되는 것이다.
여기서 valueOf는 튜토리얼의 완성도를 높이고 헷갈리는 것을 줄여주려고 언급하였다.
앞서 본 바와 같이 valueOf는 객체 자신을 반환하기 때문에 그 결과가 무시된다.
그냥 역사적인 이유때문이다. 우리는 그냥 이 메서드가 존재하지 않다고 생각하자.
이제 직접 이 메서드들을 사용한 예시를 구현해보자.
아래 user는 toString과 valueOf를 조합해 만들었는데, Symbol.toPrimitive를 사용한 위쪽 예시와 동일하게 동작한다.
출력 결과가 Symbol.toPrimitive를 사용한 예제와 완전히 동일하다는 걸 확인할 수 있다.
그런데 간혹 모든 형 변환을 한 곳에서 처리해야 하는 경우도 생긴다. 이럴 땐 아래와 같이 toString만 구현해주면 된다.
객체에 Symbol.toPrimitive와 valueOf가 없으면, toString이 모든 형 변환을 처리한다.
toString()이 항상 문자열을 반환하리라는 보장이 없고, Symbol.toPrimitive의 hint가 "number"일 때 항상 숫자형 자료가 반환되리라는 보장이 없다.
확신할 수 있는 단 한 가지는 객체가 아닌 원시값을 반환해 준다는 것뿐이다.
객체가 피연산자일 때는 다음과 같은 단계를 거쳐 형 변환이 일어난다.
그런데 이항 덧셈 연산은 위와 같은 상황에서 문자열을 연결한다.