모던 JavaScript 튜토리얼을 따라가면서 정리합니다.
4.8 객체를 원시형으로 변환하기
객체는 논리 평가 시 true
를 반환한다. 객체는 숫자형이나 문자형으로만 형변환이 일어난다고 생각하면 된다.
원시값을 기대하는 내장함수나 연산자를 사용할 때 객체-원사형으로의 형 변환이 자동으로 일어난다.
특수 객체 메서드를 사용하면 숫자형이나 문자형으로의 형변환을 원하는 대로 조절할 수 있다. 객체-원시형으로의 형 변환은 hint라고 불리는 값을 기준으로 세 종류로 구분할 수 있다. hint는 목표로 하는 자료형이다.
"string"
:alert
같이 문자열을 필요로 하는 연산, 객체를 프로퍼티 키로 사용할 때(anotherObj[obj]-123
)"number"
: 명시적 형변환(Number(obj)
), 수학 연산, 크고 작음 비교하기ex) 크고 작음을 비교할 때 쓰이는 연산자
<
,>
는 피연산자에 문자형과 숫자형을 다 허용하는데, 이 연산자들은 hint를 default가 아닌 number로 고정한다. (하위 호환성 때문에 정해진 규칙)"default"
: 연산자가 기대하는 자료형이 ‘확실치 않을 때’. 드물게 발생ex) 이항덧셈연산자
+
: 피연산자의 자료형에 따라 문자열을 합치는 연산을 할 수도 있고, 숫자를 더해주는 연산을 할 수도 있다.ex) 동등 연산자
==
를 사용해 객체-문자형, 객체-숫자형, 객체-심볼형끼리 비교할 때도, 객체를 어떤 자료형으로 바꿔야 할 지 확신이 안 서므로 hint는 default가 된다.
내장 객체는 대개 hint가 "default"
일 때와 "number"
일 때를 동일하게 처리한다. 따라서 실무에서는 hint가 "default"
인 경우와 "number"
인 경우를 합쳐서 처리하는 경우가 많다.
"boolean"
hint는 존재하지 않는다. 모든 객체는 그냥 true
로 평가된다. 우리도 내장 객체에 사용되는 규칙처럼 "default"
와 "number"
를 동일하게 처리하면, 결국엔 두 종류의 형변환(객체-문자형, 객체-숫자형)만 남게 된다.
객체-원시형 변환엔 다음의 알고리즘이 적용된다.
객체엔
obj[Symbol.toPrimitive](hint)
메서드가 있는지 찾고, 있다면 호출한다.1에 해당하지 않고 hint가
"string"
이라면obj.toString()
이나obj.valueOf
를 호출한다. (존재하는 메서드만 실행됨)1과 2에 해당하지 않고 hint가
"number"
나"default"
라면obj.valueOf()
나obj.toString()
을 호출한다. (존재하는 메서드만 실행됨)
Symbol.toPrimitive
자바스크립트에는 Symbol.toPrimitive
라는 내장 심볼이 존재하는데, 이 심볼은 목표로 하는 자료형(hint)을 명명하는 데 사용된다.
1
2
3
4
obj[Symbol.toPrimitive] = function(hint) {
// 반드시 원시값을 반환해야 한다.
// hint는 "string", "number", "default" 중 하나가 될 수 있다.
}
예시: user
객체에 객체-원시형 반환 메서드 구현하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
alert(`hint: ${hint}`);
return hint == "string" ? `{name: "${this.name}"}` : this.money;
}
};
// 데모:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500
toString과 valueOf
이 메서들은 반드시 원시값을 반환해야 한다. 객체를 반환하면 그 결과는 무시된다. 일반 객체는 기본적으로 toString
과 valueOf
에 적용되는 다음 규칙을 따른다.
toString
은 문자열"[object Object]"
를 반환한다.valueOf
는 객체 자신을 반환한다.
예시: user
는 toString
과 valueOf
를 조합해 만들었다. Symbol.toPrimitive
를 사용한 예시와 동일하게 동작한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
let user = {
name: "John",
money: 1000,
// hint가 "string"인 경우
toString() {
return `{name: "${this.name}"}`;
},
// hint가 "number"나 "default"인 경우
valueOf() {
return this.money;
}
}
간혹 모든 형변환을 한 곳에서 처리해야 하는 경우도 생긴다. 이럴 땐 toString
만 구현해준다.
1
2
3
4
5
6
7
8
9
10
let user = {
name: "John",
toString() {
return this.name;
}
};
alert(user); // toString -> John
alert(user + 500); // toString -> John500
obj.toString()
만 사용해도 ‘모든 변환’을 다 다룰 수 있기 때문에, 실무에서는 obj.toString()
만 구현해도 충분한 경우가 많다. 반환 값도 ‘사람이 읽고 이해할 수 있는’ 형식이기 때문에 실용성 측면에서 다른 메서드에 뒤쳐지지 않는다. obj.toString()
은 로깅이나 디버깅 목적으로 자주 사용된다.
반환 타입
위 세 개의 메서드는 ‘hint’에 명시된 자료형으로의 형변환을 보장해주지 않는다. 확신할 수 있는 단 한 가지는 객체가 아닌 원시값을 반환해준다는 것 뿐이다.
추가 형변환
객체가 피연산자일 때는 다음과 같은 단계를 거쳐 형변환이 일어난다.
- 객체를 원시형으로 변환한다.
- 변환 후 원시값이 원하는 형이 아닌 경우 또 다시 형변환이 일어난다.
1
2
3
4
5
6
7
let obj = {
toString() {
return "2";
}
};
alert(obj * 2); // 4, 객체가 문자열 "2"로 바뀌고, 곱셈 연산 과정에서 문자열 "2"는 숫자 2로 변경된다.