객체 - 객체를 원시형으로 변환하기

devheyrin·2022년 4월 20일
0

modern javascript

목록 보기
26/26

객체끼리 더할 때, 뺄 때, alert() 로 출력할 때 무슨 일이 발생할까? 모든 경우 객체는 원시값으로 자동 형 변환된 후 의도한 연산이 수행된다.

  1. 객체는 논리 평가에서 true 를 반환한다. 따라서 객체는 숫자형이나 문자형으로만 형 변환이 일어난다.
  2. 숫자형으로의 형 변환은 객체끼리 빼는 연산을 하거나 수학 관련 함수를 적용할 때 일어난다. 객체 Date 끼리 차감하면 두 날짜의 시간 차이가 반환된다.
  3. 문자형으로의 형 변환은 대개 객체를 출력하려고 할 때 일어난다.

ToPrimitive

특수 객체 메서드를 사용해면 숫자형이나 문자형으로의 형 변환을 원하는 대로 조절할 수 있다.

객체 형 변환은 세 종류로 구분되는데, hint 라 불리는 값이 그 기준이 된다. hint 는 ‘목표로 하는 자료형' 정도로 이해하면 된다.

string

alert() 함수처럼 문자열을 기대하는 연산을 수행할 때는 hint 가 string 이 된다.

// 객체를 출력하려고 함
alert(obj);

// 객체를 프로퍼티 키로 사용하고 있음
anotherObj[obj] = 123;

number

수학 연산을 적용하려 할 때 hint는 number 가 된다.

// 명시적 형 변환
let num = Number(obj);

// (이항 덧셈 연산을 제외한) 수학 연산
let n = +obj; // 단항 덧셈 연산
let delta = date1 - date2;

// 크고 작음 비교하기
let greater = user1 > user2;

default

연산자가 기대하는 자료형이 확실치 않을 때 hint 는 default 가 된다. 아주 드물게 발생한다.

이항 덧셈 연산자는 피연산자의 자료형에 따라 문자열을 합치는 연산을 할 수도 있고 숫자를 더해주는 연산을 할 수도 있다. 따라서 + 의 인수가 객체일 때는 hint 가 default 가 된다.

동등 연산자 == 를 사용해 객체-문자형, 객체-숫자형, 객체-심볼형끼리 비교할 때도 객체를 어떤 자료형으로 바꿔야 할지 확신이 안 서기 때문에 hint 는 default 가 된다.

// 이항 덧셈 연산은 hint로 `default`를 사용
let total = obj1 + obj2;

// obj == number 연산은 hint로 `default`를 사용
if (user == 1) { ... };

크고 작음을 비교할 때 쓰이는 연산자 <, > 역시 피연산자로 문자형과 숫자형 둘 다를 허용하는데, 이 연산자들은 hint 를 number 로 고정한다. 이는 하위 호환성 때문에 정해진 규칙이다.

이 사항들을 모두 외울 필요는 없다. Date 객체를 제외한 모든 내장 객체는 hint 가 default 인 경우와 number 인 경우를 동일하게 처리하기 때문이다. 커스텀 객체를 직접 만들 때 이러한 규칙을 따르면 된다.

형 변환 시 동작하는 알고리즘

  1. 객체에 obj[Symbol.toPrimitive](hint) 메서드가 있는지 찾고, 있다면 메서드를 호출한다.
  2. 1에 해당하지 않고 hint 가 string 이라면 obj.toString() 이나 obj.valueOf() 를 호출한다.
  3. 1과 2에 해당하지 않고 hint 가 number 나 default 라면 obj.valueOf()
    나 obj.toString() 를 호출한다.

Symbol.toPrimitive

자바스크립트에는 Symbol.toPrimitive 라는 내장 심볼이 존재하는데, 이 심볼은 아래와 같이 목표로 하는 자료형을 명명하는 데 사용된다.

obj[Symbol.toPrimitive] = function(hint) {
  // 반드시 원시값을 반환해야 한다. 
  // hint는 "string", "number", "default" 중 하나가 될 수 있습니다.
};

user 객체에 객체-원시형 변환 메서드를 구현해 보자!

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

이렇게 메서드를 구현해 놓으면 user 는 hint 에 따라 문자열로 변환되기도 하고, 숫자로 변환되기도 한다.

toString과 valueOf

toString과 valueOf 는 심볼이 생기기 이전부터 존재해왔던 ‘평범한' 메서드이다. 이 메서드를 사용하면 ‘구식'이긴 하지만 형변환을 직접 구현할 수 있다.

객체에 Symbol.toPrimitive가 없으면 자바스크립트는 아래 규칙에 따라 toString 이나 valueOf 를 호출한다.

  • hint 가 string 인 경우 : toString -> valueOf 순(toString이 있다면 toString을 호출, toString이 없다면 valueOf를 호출함)
  • 그 외: valueOf -> toString 순

이 메서드들은 반드시 원시값을 반환해야 한다. ****toString과 valueOf 가 객체를 반환하면 그 결과는 무시되고, 마치 메서드가 처음부터 없었던 것처럼 되어버린다.

일반 객체는 기본적으로 다음 규칙을 따른다.

  • toString은 문자열 "[object Object]"을 반환합니다.
  • valueOf는 객체 자신을 반환합니다.
let user = {name: "John"};

alert(user); // [object Object]
alert(user.valueOf() === user); // true

이런 이유 때문에 alert() 에 객체를 넘기면 [object Object] 가 출력된다.

아래 예시는 Symbol.toPrimitive 를 사용한 경우와 동일하게 동작한다.

let user = {
  name: "John",
  money: 1000,

  // hint가 "string"인 경우
  toString() {
    return `{name: "${this.name}"}`;
  },

  // hint가 "number"나 "default"인 경우
  valueOf() {
    return this.money;
  }

};

alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500

간혹 모든 형 변환을 한 곳에서 처리해야 하는 경우가 있다. 이럴 때는 toString 만 구현해 주면 모든 형 변환을 처리해준다.

let user = {
  name: "John",

  toString() {
    return this.name;
  }
};

alert(user); // toString -> John
alert(user + 500); // toString -> John500

반환 타입

위에서 소개한 세 개의 메서드는 ‘hint’ 에 명시된 자료형으로의 형 변환을 보장해 주지 않는다. 확신할 수 있는 단 하나는 객체가 아닌 원시값을 반환해 준다는 것이다.

추가 형 변환

객채가 피연산자일 때, 다음과 같은 단계를 거친다.

  1. 객체는 원시형으로 변화된다.
  2. 변환 후 원시값이 원하는 형이 아닌 경우 또 다시 형 변환이 일어난다.
let obj = {
  // toString에서 모든 형 변환을 처리
  toString() {
    return "2";
  }
};

alert(obj * 2); 
// 4, 객체가 문자열 "2"로 바뀌고, 
// 곱셈 연산 과정에서 문자열 "2"는 숫자 2로 변환
  1. obj * 2 에서는 객체가 원시형으로 변환되므로 문자열 “2”가 된다.
  2. 곱셈 연산은 문자열을 숫자형으로 변환시키므로 “2” → 2가 된다.

이항 덧셈 연산은 위와 같은 상황에서 문자열을 연결한다.

let obj = {
  toString() {
    return "2";
  }
};

alert(obj + 2); 
// 22("2" + 2), 문자열이 반환되기 때문에 문자열끼리의 병합이 일어난다.

hint

연산자별로 어떤 hint 가 적용되는지는 명세서에서 찾아볼 수 있다. 연산자가 기대하는 피연산자인지를 ‘확신할 수 없을 때' hint 는 default 가 되는데, 이런 경우는 아주 드물게 발생한다. 내장 객체들은 대개 hint 가 default 일 때와 number 일 때를 동일하게 처리하므로, 실무에서는 두 경우를 합쳐서 처리하는 경우가 많다.

profile
개발자 헤이린

0개의 댓글