DEV Community

Composite
Composite

Posted on

valueOf 메소드, 뭐하러 있냐?

언제부턴가 dev.to 사이트에 4개 제한했던 태그 개수 늘려줬네...

자, 개발을 하다보면 자동완성 하다보면,
일단 toString() 메소드는 많이 익숙할 것이다.
네가 만든 객체 및 클래스를 문자열로 표현하는 것. 그것은 익숙하니까. (끄덕)
그럼 valueOf() 메소드는 왜 있는가?
나답게 쿨거래하듯이 설명해 주겠다.
따라와라.

Date 객체 많이들 쓰고 있을 것이다. 이것이 편하든 불편하든 간에 많이들 쓸 것이다.
Date 객체를 다루다 보면 알게 되는게,

  • +new Date() 가 먹힌다. 결과는 new Date().getTime() 과 동일하다.
  • Date 객체 간 비교가 가능하다. oldDate < newDate 식이 잘 먹힌다.

이러하다. 이게 가능한 이유가 바로 valueOf() 이다.
자, Date.prototype.getTime() 메소드를 호출하면 나오는 유닉스 밀리초 숫자가 나온다. JS 에서 날짜 비교하거나 뭐 여러가지 다양한 용도에 활용할 수 있다.
그럼 +new Date() 가 왜 먹히냐면, 더하기 문자가 valueOf() 메소드의 축약어로 사용할 수 있기 때문이다.
그렇다면 < > 등의 비교문을 가능하게 한 것도? 맞다. valueOf() 메소드를 호출해서 비교하기 때문에 가능하다는 것이다.
좋다. valueOf() 메소드의 설명을 MDN에서 보도록 하자.

valueOf() 메서드는 특정 객체의 원시 값을 반환합니다.

원시 값이라면 생각나는게 숫자일 것이다. 부울 값도 있고.
자바스크립트는 추가적으로 [] 배열 식과, "문자열" 문자열 식도 원시 값으로 취급한다. 심지어 {} 같은 객체 식도. 원시 객체라 한다.
따라서, 문자열의 경우 "문자열".valueOf() 호출하면 문자열 그대로 나올 것이다.
어쨌든, 원시 값이면 된다고 보면 된다. 물론 보통 숫자를 많이 쓸 것이다. 여러모로 유용하니까.

그렇다면, 자신의 객체나 클래스에 valueOf() 재정의 하고 싶다?
쉽다. toString() 재정의 기억나는가? 그거와 똑같이 하면 된다.

MyObj.prototype.valueOf = function() {
  return this.numberValue; // 보통 숫자로 쓰겠지?
}
Enter fullscreen mode Exit fullscreen mode

물론 문자열이어도 되고, 부울값이나 심지어 배열이어도 된다.
그렇다면 이녀석의 활용은?

가장 대표적인 활용처라 하면 '비교'가 되겠다.

자바는 Comparable<V>, 닷넷은 IComparable<V>(보다는 그냥 연산자 오버로딩 써도 됨)이나 파이썬의 연산자 데코레이터(__le__ 등) 등등... 뭐 언어별로 다양한 비교 방법을 제공한다.
자바스크립트는 연산자 오버로딩도, 시스템 클래스가 제공하는 데코레이션도 없지만, valueOf() 메소드에 비교 가능한 원시 값(주로 숫자)을 뿌리면, 비교 연산을 통해 손쉽게 비교 가능하다. Date 객체처럼 말이다.

마지막으로, 아마 이런 의문을 들 것이다.
만약 문자열이 원시 값이라면, toString() 이 있는데, 이거 쓰는 것과 무슨 차이가 있냐고 의문이 들 것이다.
이건, ECMAScript 영문 공식 문서 11.6.1 The Addition operator ( + ) 에서 답을 찾을 수 있는데,

The reason why ("x="+x) gives "x=value" and not "x=tostring" is the following. When evaluating "+", javascript first collects primitive values of the operands, and then decides if addition or concatenation should be applied, based on the type of each primitive.

So, this is how you think it works

a +
a + b:
    pa = ToPrimitive(a)
    pb = ToPrimitive(b)*
    if(pa is string || pb is string)
       return concat(ToString(pa), ToString(pb))
    else
       return add(ToNumber(pa), ToNumber(pb))
Enter fullscreen mode Exit fullscreen mode

연산자가 들어가면, 원시 값을 찾으려 애를 쓰기 시작한다.
여기서 자바스크립트에서 원시 값을 정의하는 메소드라면 valueOf 가 있을 것이다. 바로 여기서 반환되는 값을 우선 짚어서 연산을 시도한다. 예를 들어 숫자끼리 더하고, 문자열 들어라면 문자열에 추가하는 등 말이다.
또한, 여기서 알 수 있는 사실은, 문자열이 우선 적용되어, 연산자와 피연산자 중 하나라도 문자열이 들어가는 순간 문자열 첨가를 시도하려 한다는 것을 알 수 있다. 그다음 숫자로 변환을 시도해서 연산을 시도한다.
만약 이도저도 아니라면? TypeError 오류가 널 반겨줄 것이다.

Here's a little more detail, before I get to the answer:

var x = {
    toString: function () { return "foo"; },
    valueOf: function () { return 42; }
};

alert(x); // foo
"x=" + x; // "x=42"
x + "=x"; // "42=x"
x + "1"; // 421
x +
var x = {
    toString: function () { return "foo"; },
    valueOf: function () { return 42; }
};

alert(x); // foo
"x=" + x; // "x=42"
x + "=x"; // "42=x"
x + "1"; // 421
x + 1; // 43
["x=", x].join(""); // "x=foo"
Enter fullscreen mode Exit fullscreen mode

만약 toString() 메소드와 valueOf() 메소드를 모두 재정의 했다면, 연산자 및 피연산자의 우선순위는 valueOf() 메소드 결과 값이 되는 것이다.
그러면, alert(myobj)['1',2,myObj].join(',') 식을 수행했을 때는 toString() 으로 정의된 문자열로 나오는데?
그건 바로 그 함수에서 문자열을 가져와야 하기 때문에 toString() 메소드를 쓴 것이다.
그러니까 이렇게 정리하면 된다.

  • valueOf() 연산을 위한 값. 원시 값을 뱉어서 연산에 필요한 기능 제공!
  • toString() 문자열로 표시할 값. 오로지 문자열로만!

만약 문자열만 표시할 목적이라면, toString() 메소드를 쓰면 된다. 이 메소드만 써도 + 등의 문자열 첨가 연산에 대응 가능하다.
만약 비교 등의 객체의 원시 기능 역할을 수행하고자 한다면, valueOf() 메소드를 쓰면 된다. 숫자를 제공해서 비교에 쓰이거나, 문자열을 뱉어내도 되고, 배열이나 원시 객체 뱉어내도 되고... 원시 값을 통한 적절한 연산에 필요한 값을 뿌리면 되는 거다.

자, 이제 정리가 됐는가? 그럼 됐다. 난 그냥 보람을 느끼겠다.
끗.

2021-10-26
아 한가지 깜박한게 있는데, 너희들도 알다시피 문자열도 비교 연산이 가능하다. 그렇다면 굳이 재정의 안해도? 대응 가능하다. 만약 너의 쌩 객체에 valueOf() 메소드 호출하면 [object yourType] 같은 형식을 가진 문자열이 표출되니까. 그래서 재정의를 안해도 < > 같은 비교식이 먹힌다. 물론 그 결과가 옳은지는 며느리도 모른다. (대충 틀니 소리)

끗_최종.hwp

Top comments (2)

Collapse
 
jeongtae profile image
Jeongtae Kim

좋은 글 감사합니다. valueOf()의 존재 의미를 잘 몰랐는데, 한번에 쉽게 이해할 수 있었습니다. 그런데 마지막에 첨언하신 내용에서, 생 객체에 valueOf() 메서드를 호출하면 "[object Object]"같은 문자열이 표출된다고 하셨는데, toString()을 써야할 것을 valueOf()로 잘못 적으신게 아닌가 해서 댓글 적어봅니다.

Collapse
 
composite profile image
Composite

흠... 다시보니 그렇군요. valueOf 미정의 시 객체 자체를 뱉게 됩니다.
따라서 alert 함수 호출하면 무조건 문자열 가져오기 위해 인자.toString() 메소드 호출해서 뿌리니까요.
생각난 김에 재밌는 코드를 하나 만들어 봤습니다.

function Wow() {
  this.toString = () => "Wow"
}

alert(new Wow().valueOf())
Enter fullscreen mode Exit fullscreen mode

결과적으로는 Wow 라는 경고창이 뜰텐데, new Wow().valueOf().toString() 결과를 뱉어내게 되니 결국 자신을 뱉어내고 toString() 메소드를 호출하죠.

하지만 문제는, 제가 왜 저딴식으로 작성했는지 오래되어 기억이 안난다는 거죠.
지적 감사합니다. 근데 고치기 귀찮군요. 고치자니 재미도 없고 감동도 없어서.