[JS] 객체를 원시형으로 변환

MJ·2022년 9월 8일
0

Java Script

목록 보기
37/57
post-thumbnail

객체 원시형 변환

obj + obj2 같은 객체간의 덧셈이나 뺄셈 같은 연산이 수행되면, 객체는 원시값으로
변환되고, 이후의 연산이 수행된다.


객체 연산 수행 과정

  • 객체를 논리 연산자로 평가할 시, 무조건 true를 반환한다.
  • 모든 객체는 숫자형이나 문자형으로만 형 변환해서 연산을 수행한다.
  • 객체를 덧셈이나 뺄셈같은 수학적인 연산을 수행하면, 숫자형으로 형 변환한다.
  • 문자형으로 형 변환은 alert(obj) 같이 객체를 출력하려고 할 떄, 형 변환 됩니다.



ToPrimitive

ToPrimitive 메서드를 사용하면 객체를 원시형으로 형 변환 할 수 있습니다.
객체의 형 변환은 3종류로 구분 되는데, 구분의 되는 기준을 hint라고 합니다.
객체가 어떤 연산을 통해서 원시형으로 형 변환하는데, 형 변환되는 자료형을 hint
라고 부릅니다.

쉽게 말해 산술적인 연산을 하면 hint 는 숫자형이 되고, alert 같이 문자열을 출력
하면 hint는 문자형이 됩니다. 연산의 결과가 숫자인지 문자인지 확실하지 않은 경우
hintdefault가 됩니다.

🔔 힌트란?
객체가 형 변환 하려는 자료형정도로 이해 합시다. 자세한 것은 명세서 참조 바랍니다.


🔔 객체의 형 변환 3종류

string(문자형) number(숫자형) default(알 수 없음)


String

alert() 함수 같이 객체를 문자형으로 변환하는 연산을 수행하는 경우
hintString 이 됩니다. 아래 예제는 hintString이 되는 경우 입니다.

alert(obj);	// 이 함수에 인자값은 어떤 자료형이든 출력 시, 문자열로 형 변환한다.

obj2[obj] = 123;	// obj 객체를 키로 사용한다.


/* */
obj2[obj] = 123;
1) ob2 객체에 프로퍼티 키로 obj 객체를 할당한다. 
2) 객체명이 할당되서, 객체명으로 접근할 수 있음 obj[obj]
	let obj2 = { 
     [obj]: 123
    };

Number

객체를 숫자형으로 형 변환하는 수학연산을 수행할 때 hintnumber가 된다.
아래 예제는 hintNumber가 되는 경우 입니다.

let num = Number(obj);	// obj 객체를 숫자형으로 형 뱐환

let n = +obj;	// obj 객체를 숫자형으로 형 변환
let delta = date - date2;	// date 객체와 date2 객체간의 뺼셈 연산 > 숫자형 형 변환


let greater = user1 > user2 // 객체간의 크기 비교 > 숫자형 형 변환

Default

연산 수행을 통해 객체가 어떤 자료형으로 변할 지 확실하지 않은 경우 hintdefeualt
가 됩니다. 예를 들어 이항 덧셈 연산자에서 +은 피연산자의 자료형에 따라서 문자열을
합칠 수도 있고, 숫자를 더해주는 연산을 할 수도 있다. 어떤 자료형이 결과가 될지 확실치
않으므로 hintdefault가 된다.

또는, 동등 연산자를 사용해서 객체-문자형, 객체-숫자형, 객체-심볼형끼리 비교할 때도 객체를
어떤 자료형으로 바꿔야 할지 확실치 않으므로 hintdefault가 됩니다.

let total = obj1 + obj2;	// 이항 덧셈 연산자에서 `hint`는	 `default`가 된다 
							

if ( user == 1 ) { ... };	// 동등 연산자는 hint로 default를 사용

🔔 > < 같은 비교 연산자

크고 작음을 비교할 때 사용하는 비교 연산자는 피연산자로 문자형, 숫자형 둘 다
허용합니다. 어떤 자료형으로 비교할 지 확실치 않지만, hintnumber로 고정 된다.
원래 같으면 default가 되는게 맞지만, 특수한 규칙으로 인해 객체를 비교할 때는
hint = number가 됩니다.


이런 규칙들을 모두 외울 필요는 없습니다. Date 객체를 제외한 모든 내장 객체는
hintdefault 또는 number인 경우 동일하게 처리하기 때문입니다. 우리도
커스텀한 객체를 만들 때 이 규칙을 따르면 됩니다.

⚠️ 논리형 hint는 없다.

모든 객체는 true로 평가되기 때문에, hint가 논리형인 경우는 없습니다.
내장 객체에서 사용하는 규칙처럼 hintdefault라면 number과 동일하게 처리하면
됩니다. 결국엔 numberstring두 종류의 형 변환만 남게 되죠.



자바스크립트 형 변환 규칙

JS에서는 객체가 형 변환하려고 할 때, 아래와 같은 알고리즘에 따라서 메서드를 호출합니다.

  1. 객체에 obj[symbol.toPrimitive](hint) 메서드를 호출합니다. Symbol.toPrimitive
    는 시스템 심볼로, 심볼형 키를 사용합니다.

  2. symbol. 메서드가 없고, hintString이라면 obj.toString()을 호출하고
    없다면 obj.valueOf()를 호출합니다.

  3. symbol. 메서드가 없고, hintnumber 또는 default라면 obj.valueOf()
    호출하고 없다면 obj.toString()를 호출합니다.

  4. 모든 메서드가 존재하지 않는다면 자동으로 형 변환 할 수 없다.

✅ 호출한다고 없는 메서드를 호출하는 것이 아니라, 존재하는 메서드만 호출합니다.



Symbol.toPrimitive

Symbol.toPrimitive은 시스템 심볼이며, 이 심볼은 목표로 하는 자료형(hint)을 명명
하는데 사용합니다.

/* 구문 */
obj[Symbol.toPrimitive] = function(hint) { 
  // 반드시 원시값을 반환해야 한다.
  // hint는 string, number, default 중 하나가 될 수 있다.
  // function 키워드는 생략될 수 있다.
}

설명으로는 이해하기 부족합니다. 실제 예시를 살펴보겠습니다.

let user = {
 name: "John",
 money: 50000,
  
 [Symbol.toPrimitive](hint) {	// 여기서 hint라는 키는 다른 이름으로 변경해도 됩니다.
  alert(`hint: $[hint]`);	// 현재 출력되는 hint를 보여줍니다.
  return ( hint == 'string' ) ? this.name : this.money;	
// hint가 문자열이면 John, 아니라면 50000 출력, default인 경우도 50000 출력
  },
  
};

alert(user);	// hint: string -> John  출력
alert(+user);	// hint: number -> 50000 출력
alert(user + 1);	// hint: default -> 50001 출력 


/* */
alert(user)
1. alert는 ( ) 괄호안의 값이 어떤 자료형이던 문자열로 형 변환 한다 
2. alert로 인해 객체가 목표로 하는 자료형(hint)가 문자형이 된다.
3. 객체가 원시형(문자)으로 형 변환 하기 위해 Symbol.toPrimitive 메서드를 호출한다.
4. toPrimitive() 인자 값으로는, `hint(String)`가 전달 됨 
  > this.name > John이 출력 된다.

    
alert(+user);
1. 단항 연산자로 인해, 객체가 목표로 하는 자료형(hint)가 숫자형이 된다.
2. 객체가 원시형(문자)으로 형 변환 하기 위해 Symbol.toPrimitive 메서드를 호출한다.
3. toPrimitive() 인자 값으로 `hint(Number)`를 전달 
  > this.money > 50000이 출력 된다.

  
alert(user + 1)
1. 이항 연산자는 객체가 어떤 자료형을 목표로 하는 지 알 수 없음 > `hint``default`
2. 객체가 원시형(문자)으로 형 변환 하기 위해 Symbol.toPrimitive 메서드를 호출한다.
3. toPrimitive() 인자 값으로 `hint(default)`를 전달 > default는 Number와 동일하게
작동하게 만들었음. > this.money > 50000 출력
4. 50000 + 1 = 50001이 된다.


/* */
`hint`는 객체가 어떤 자료형을 목표로 형 변환 하는지에 대한 값이 들어 있다.

[Symbol.toPrimitive]() 메서드의 인자 값으로 받는 `hint`는 암시적으로 정해놓은 것이므로
(hint를 'hard')라고 변경해도 됨, 다만 이름 자체는 의미 있게 사용하는 것이 좋습니다.

이렇게 구현하면, user 객체는 hint에 따라서 문자열로 또는 숫자형으로 변환 합니다. [Symbol.toPrimitive)(hint) 메서드 하나로, 객체에 대한 형 변환을 다룰 수 있습니다.


toString()과 valueOf()

toString valueOf는 심볼이 생기기 이전부터 존재한 평범한 메서드 입니다.
이 메서드를 이용하면 형 변환을 직접 구현할 수 있습니다.

JS는, 객체에 Symbol.Primitive()가 없다면 toString이나 valueOf를 호출합니다.

생성된 메서드는, 반드시 원시값을 반환해야 한다. toString이나 valueOf가 객체를 반환하면
그 결과는 무시됩니다. 메서드가 처음부터 없었던 것이 됩니다.

호출에는 순서가 있다.

✔️ hintstring인 경우
toString > valueOf ( toString이 없다면 valueOf 호출 )


✔️ 그 외의 경우
valueOf > toString ( valueOf가 없다면 toString 호출 )


일반 객체는 기본적으로 toStringvalueOf에 적용되는 규칙을 따릅니다.

toStringvalueOf에 적용되는 규칙

toString은 문자열 "[object Object]"를 반환한다. > 객체의 프로퍼티가 숨겨짐
valueOf는 객체 자신을 반환한다.


hintString인 경우

아래 예제는 객체를 원시형으로 형 변환 해주는 메서드가 없는 경우입니다.
이 경우, 상위 객체로 부터 hint에 맞는 메서드를 자동 호출하게 됩니다.

let user = { name: "John" };

alert(user);	// {object Object}
alert(user.valueOf() === user);	// true, valueOf()는 객체 자신을 반환하기 때문에
// 메서드를 호출한 것 자체가 없어진다. 즉 user === user를 비교하게 되는 것

/* */
alert(user);
1. alert로 인해 객체가 목표로 하는 자료형이 String이 된다. ( hint = string )
2. Symbol.toPrimitive 메서드가 있는지 확인 > 없다 
3. toString()을 메서드 탐색 > 없다 | valueOf() 메서드 탐색 > 없다
4. user 라는 객체에는 toString() 메서드가 없기에, 자신의 상위 객체(object)를 찾아서
  toString() 메서드를 상속받아 옵니다. 즉 자동으로 호출되고 사라진다.

5. 결론은 toString()으로 인해 {object Object}가 출력 됩니다.

❗ hint가 숫자형이나 다른 자료형이였다면 valueOf()메서드가 호출됩니다.

⚠️ 오해하지 말기

user 객체의 프로퍼티에 name이 문자열 값이라 호출되는게 아닙니다. 저 값은 아무런
의미가 없습니다. 오직 hint만 확인합니다.


이번에는 toString()valueOf()를 사용해서 객체를 직접 형 변환 해보겠습니다.

let user = {
 name: "John",
 money: 10000,
 
 // hint가 string인 경우
 toString() {
  return '내 이름은:' + this.name; 
 },
  
  // hint가 string이 아닌 경우 ( number or default )
  valueOf() {
   return this.money; 
  },
  
};

alert(user);	// hint: string > 내 이름은: John 출력
alert(+user);	// hint: number > 10000 출력
alert(user + 10000);	// hint: default > 20000 출력


/* */
`Symbol.toPrimitive` 메서드를 사용한 예제와 동일하게 작동합니다.

모든 자료형을 문자형으로 형 변환

모든 형 변환을 한 곳에서 처리해야 하는 경우도 있습니다. 이런 경우에는 toString()
구현해주면 됩니다. 객체에 Symbol.toPrimitive 또는 valueOf 메서드가 없는 경우엔
toString 메서드가 모든 형 변환을 문자열로 변환시킵니다.

let user = {
  name: "John",
  
  toString() {
   return this.name; 
  }
};

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



반환 타입

객체를 형 변환 시키는 3개의 메서드는, hint에 명시된 자료형으로의 형 변환을 보장해주지
않습니다. toString()이 항상 문자열을 반환한다는 보장이 없고, Symbol.toPrimitive
hintnumber인 경우에 항상 숫자형을 반환한다는 보장도 없습니다.

확실한 것은, 객체를 원시형의 값으로 반환해 준다는 것 입니다.



객체간의 곱셈 연산

피연산자가 산술적인 연산을 수행하면, 자료형이 숫자형으로 변환 됩니다.
* 곱셈 연산 또한 산술 연산이므로 피연산자가 숫자형으로 형 변환 합니다.
이 규칙은 객체도 포함 됩니다.

  1. 객체를 *연산하면, 객체가 원시형으로 변환 됩니다.
  2. 변환 후 원시값이 원하는 형이 아닌 경우에는 다시 형 변환 합니다.
let obj = {	
  // 다른 메서드 valueOf등이 없다면, toString이 모든 자료형을 문자열로 형 변환한다.
  toString() {
   return "2"; 
  }
  
};

alert( obj * 2 ); // 4

/* */
alert( obj * 2 );
1. obj 객체의 `hint``default` > valueOf나 Symbol 메서드가 없다 > toString() 호출
2. 문자열 2가 반환되어 연산 수행 > "2" * 2 > 곱셈 연산은 피연산자를 숫자형으로 변환시킴
3. 2 * 2 = 4가 된다.

객체간의 덧셈 연산 (이항 연산자)

이항 연산자에서 피연산자를 덧셈 연산하는 과정중에 피연산자가 문자열이라면 덧셈 대신
문자열 병합이 발생합니다. 객체도 이 규칙에 해당 됩니다.

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

alert( obj + 2 );	// 문자열 "22" 출력


/* */
alert( obj + 2 );
1. obj 객체의 `hint``default` > 다른 메서드가 없기에 toString() 호출
2. 문자열 "2"를 반환 후 연산 수행 > "2" + 2 > 문자열에서 이항 덧셈 연산자는 병합이 일어남
3. "2" + "2" > "22"가 된다.



정리

원시 값을 기대하는 내장 함수나(alert) 연산자를 사용할 때 객체는 원시형으로 자동 형 변환됨.
객체가 원시형으로 형 변환 될 때, 기준이 되는 값은 hint라고 부른다.


🔔 객체 -> 원시형으로 형 변환 3가지 방식 ( 3종류의 hint )

✔️ String
alert 함수 같이 문자열을 필요로 하는 연산

✔️ Number
수학 연산이 이루어 질 때 ( 이항 덧셈 연산자 제외 )

✔️ Default
연산의 결과가 어떤 자료형일지 모르는 경우 ( 숫자형+문자형 등등 )


🔔 객체 -> 원시형으로 형 변환 알고리즘

1. 객체가 원시값으로 형 변환 되려고 하면 먼저 Symbol.toPrimitive() 메서드를 객체
내부에서 찾는다. 있다면 호출하고 없으면 다음 단계 수행

2. hint의 값이 String인 경우 객체 내부에서 toString() 메서드를 찾는다. 있다면
호출하고, 없다면 valueof() 메서드를 호출

3. hint의 값이 Number || Default인 경우 객체 내부에서 valueOf() 메서드를 찾는다.
있다면 호출하고, 없다면 toString() 메서드를 호출


⚠️ 형 변환이 되는 메서드가 객체 내부에서 존재하지 않는다면, hint의 값에 알맞은
메서드를 상위 객체로부터 상속받아 온다. 문자형이라면 toString() 숫자형이나 알 수
없는 경우에는 valueOf()를 상속 받아 오는지는 확실하지 않다.
( 추후에 수정 예정 or 댓글로 알려주세요 )


obj.toString()만 사용해도 '모든 변환’을 다룰 수 있기 때문에, 실무에선 obj.toString()
구현해도 충분한 경우가 많습니다. 반환 값도 ‘사람이 읽고 이해할 수 있는’ 형식이기 때문에
실용성 측면에서 다른 메서드에 뒤처지지 않습니다. obj.toString()은 로깅이나 디버깅
목적으로도 자주 사용됩니다.

profile
프론트엔드 개발자가 되기 위한 학습 과정을 정리하는 블로그

0개의 댓글