Javascript의 valueOf () 대 toString ()


115

Javascript에서 모든 객체에는 valueOf () 및 toString () 메서드가 있습니다. 문자열 변환이 호출 될 때마다 toString () 메서드가 호출된다고 생각했을 것입니다.

예를 들어, 코드

var x = {toString: function() {return "foo"; },
         valueOf: function() {return 42; }};
window.console.log ("x="+x);
window.console.log ("x="+x.toString());

인쇄됩니다

x=42
x=foo

예를 들어 x가 복소수이면 valueOf ()가 그 크기를 제공하기를 원하지만 문자열로 변환하고 싶을 때마다 "a + bi"와 같은 것을 원합니다. 그리고 문자열을 암시하는 컨텍스트에서 toString ()을 명시 적으로 호출하고 싶지 않습니다.

이것이 바로 그럴까요?


6
window.console.log (x);또는 시도 했습니까 alert (x);?
Li0liQ

5
그들은 각각 "Object"와 "foo"를 제공합니다. 재미있는 것들.
brainjam

사실, alert (x); "foo"를 제공하고 window.console.log (x); Firebug에서 "foo {}"를 제공하고 Chrome 콘솔에서 전체 개체를 제공합니다.
brainjam

에서 파이어 폭스 33.0.2 alert(x)표시 foowindow.console.log(x)표시합니다 Object { toString: x.toString(), valueOf: x.valueOf() }.
John Sonderson 2014

답변:


107

( "x ="+ x)가 "x = tostring"이 아닌 "x = value"를 제공하는 이유는 다음과 같습니다. "+"를 평가할 때 javascript는 먼저 피연산자의 기본 값을 수집 한 다음 각 기본 유형에 따라 더하기 또는 연결을 적용할지 여부를 결정합니다.

그래서 이것이 작동한다고 생각하는 방법입니다.

a + b:
    pa = ToPrimitive(a)
    if(pa is string)
       return concat(pa, ToString(b))
    else
       return add(pa, ToNumber(b))

그리고 이것은 실제로 일어나는 일입니다

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))

즉, toString은 원래 객체가 아닌 valueOf의 결과에 적용됩니다.

자세한 내용 은 ECMAScript 언어 사양의 11.6.1 더하기 연산자 (+) 섹션을 참조하십시오 .


* 문자열 컨텍스트에서 호출 될 때 ToPrimitive toString을 호출하지만 '+'는 유형 컨텍스트를 적용하지 않기 때문에 여기서는 그렇지 않습니다.


3
"실제로"블록의 조건이 "if (pa is string && pb is string)"로 읽어야하지 않습니까? 즉 "||"대신 "&&" ?
brainjam

3
표준은 확실히 "또는"(링크 참조)이라고 말합니다.
user187291

2
예, 맞습니다. 연결시 다른 유형보다 문자열에 우선 순위가 부여됩니다. 경우 피연산자가 문자열, 전체 것은 문자열로 연결될 수 있습니다. 좋은 대답입니다.
devios1

76

답을 찾기 전에 조금 더 자세히 설명하겠습니다.

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"

toString기능은 일반적으로 "트럼프" 가 아닙니다valueOf . ECMAScript 표준은 실제로이 질문에 꽤 잘 대답합니다. 모든 개체에는 [[DefaultValue]]요청시 계산되는 속성이 있습니다. 이 속성을 요청할 때 인터프리터는 예상되는 값의 종류에 대한 "힌트"도 제공합니다. 힌트가 String이면 toString은 앞에 사용됩니다 valueOf. 그러나 힌트가 Number이면valueOf 먼저 사용됩니다. 하나만 존재하거나 기본이 아닌 항목을 반환하는 경우 일반적으로 다른 항목을 두 번째 선택 항목으로 호출합니다.

그만큼 +작업자는 항상 힌트를 제공하는 Number첫 번째 피연산자 문자열 값이더라도. 비록 그것이 요청 x의 대한 Number첫 번째 피연산자가 문자열에서 반환하기 때문에, 표현 [[DefaultValue]]은 문자열 연결을 수행합니다.

toString문자열 연결을 위해 호출 되도록 보장 하려면 배열과 .join("")메서드를 사용하십시오.

(ActionScript 3.0은의 동작을 약간 수정합니다 +. 피연산자가 a String이면이를 문자열 연결 연산자로 처리하고을 String호출 할 때 힌트를 사용 합니다 [[DefaultValue]]. 따라서 AS3에서이 예제는 "foo, x = foo, foo"를 생성 합니다 . = x, foo1, 43, x = foo ".)


1
또한 기본이 아닌 값을 반환 valueOf하거나 toString반환 하면 무시됩니다. 둘 다 존재하지 않거나 둘 다 프리미티브를 반환하지 않으면 a TypeError가 발생합니다.
bcherry

1
감사합니다 bcherry, 이것이 내가 바라던 대답의 구경입니다. 그러나 x + "x ="는 안됩니다. 수익률 "42x ="? 그리고 x + "1"; 수율 421? 또한 ECMAScript 표준의 관련 부분에 대한 URL이 있습니까?
brainjam

2
사실, '+'힌트 ($ 11.6.1 참조) 그러므로 ToPrimitive를 발동 사용하지 않는 [[DefaultValue]](no-hint)동일합니다, [[DefaultValue]](number).
user187291

9
이것은 기본 제공 Date 클래스의 경우가 아닌 것 같습니다. ("" + new Date(0)) === new Date(0).toString(). Date 객체는 toString()무언가에 추가 될 때 항상 그 값 을 반환하는 것처럼 보입니다 .
kpozin

7
+1 & Thx! 이 답변에 대해 자세히 설명 하고 여기에 링크 / 공유하고 싶었던 블로그 게시물을 찾았 습니다. (Dmitry A. Soshnikov의 의견 포함)이 답변에 정말 도움이되었습니다.
GitaarLAB 2013 년

1

TLDR

형식 강제 또는 암시 적 형식 변환은 약한 형식 지정을 가능하게하며 JavaScript 전체에서 사용됩니다. 대부분의 연산자 (엄격한 같음 연산자 ===!==) 및 값 검사 작업 (예 if(value)...:)은 해당 값의 유형이 작업과 즉시 호환되지 않는 경우 값을 강제로 제공합니다.

값을 강제하는 데 사용되는 정확한 메커니즘은 평가되는 식에 따라 다릅니다. 질문에서 더하기 연산자 가 사용됩니다.

더하기 연산자는 먼저 두 피연산자가 프리미티브인지 확인합니다.이 경우 valueOf메서드 호출이 포함됩니다 . toString재정의하기 때문에 방법이 경우에 호출되지 않습니다 valueOf개체에 대한 방법은 x원시 값을 반환합니다.

그런 다음 질문의 피연산자 중 하나가 문자열이기 때문에 피연산자가 모두 문자열 로 변환됩니다. 이 프로세스는 추상적 인 내부 작업 ToString(참고 : 대문자) toString을 사용하며 객체 (또는 프로토 타입 체인) 의 메서드 와는 다릅니다 .

마지막으로 결과 문자열이 연결됩니다.

세부

JavaScript의 모든 언어 유형 (예 : Number, BigInt, String, Boolean, Symbol 및 Object)에 해당하는 모든 생성자 함수 객체의 프로토 타입에는 valueOftoString.

의 목적은 valueOf객체와 연관된 기본 값을 검색하는 것입니다 (있는 경우). 객체에 기본 프리미티브 값이 없으면 객체가 단순히 반환됩니다.

valueOf프리미티브에 대해 호출되면 프리미티브가 정상적인 방식으로 자동 박스 화되고 기본 프리미티브 값이 리턴됩니다. 문자열의 경우 기본 기본 값 (즉,에서 반환 된 값 valueOf)은 문자열 표현 자체입니다.

다음 코드는 valueOf메서드가 래퍼 개체에서 기본 기본 값을 반환하고 기본 요소에 해당하지 않는 수정되지 않은 개체 인스턴스가 반환 할 기본 값이 없어서 단순히 자신을 반환하는 방법을 보여줍니다.

console.log(typeof new Boolean(true)) // 'object'
console.log(typeof new Boolean(true).valueOf()) // 'boolean'
console.log(({}).valueOf()) // {} (no primitive value to return)

반면에의 목적은 toString객체의 문자열 표현을 반환하는 것입니다.

예를 들면 :

console.log({}.toString()) // '[object Object]'
console.log(new Number(1).toString()) // '1'

대부분의 작업에서 JavaScript는 하나 이상의 피연산자 (들)를 필요한 유형으로 자동 변환하려고 시도합니다. 이 동작은 JavaScript를 더 쉽게 사용할 수 있도록 선택되었습니다. 자바 스크립트는 처음에는 예외가 없었고 , 이것이 디자인 결정에 영향을 미쳤을 수도 있습니다. 이러한 종류의 암시 적 유형 변환을 유형 강제 변환이라고하며 이는 JavaScript의 느슨한 (약한) 유형 시스템의 기초입니다. 이 동작 뒤에있는 복잡한 규칙은 형변환의 복잡성을 언어 자체로, 그리고 코드 밖으로 옮기기위한 것입니다.

강제 프로세스 중에 발생할 수있는 두 가지 변환 모드가 있습니다.

  1. 객체를 프리미티브로 변환 (유형 변환 자체가 포함될 수 있음)
  2. 프리미티브 타입 중 하나의 생성자 함수 객체를 사용하여 특정 유형의 인스턴스에 대한 직접 변환 (예. Number(), Boolean(), String()등)

프리미티브로 변환

프리미티브가 아닌 유형을 작동 될 프리미티브로 변환하려고 시도 할 때 ToPrimitive'숫자'또는 '문자열'의 선택적 "힌트"와 함께 추상 연산 이 호출됩니다. 힌트를 생략하면 기본 힌트는 '숫자'입니다 ( @@toPrimitive메소드가 재정의 되지 않은 경우 ). 힌트가 '문자열'이면 toString먼저 시도되고 , 프리미티브를 반환하지 않으면 valueOf두 번째로 시도 toString됩니다. 그렇지 않으면 그 반대의 경우도 마찬가지입니다. 힌트는 변환을 요청하는 작업에 따라 다릅니다.

더하기 연산자는 힌트를 제공하지 않으므로 valueOf먼저 시도합니다. 빼기 연산자는 '숫자'의 힌트를 제공하므로 valueOf먼저 시도됩니다. 힌트가 '문자열'인 사양에서 찾을 수있는 유일한 상황은 다음과 같습니다.

  1. Object#toString
  2. ToPropertyKey인수를 속성 키로 사용할 수있는 값으로 변환하는 추상 연산

직접 유형 변환

각 운영자는 작업을 완료하기위한 자체 규칙이 있습니다. 더하기 연산자는 먼저 ToPrimitive각 피연산자가 프리미티브인지 확인 하는 데 사용 됩니다. 그런 다음 피연산자 중 하나가 문자열이면 의도적 ToString으로 각 피연산자에 대해 추상 연산 을 호출 하여 문자열로 예상되는 문자열 연결 동작을 제공합니다. ToPrimitive단계 후에 두 피연산자가 모두 문자열이 아니면 산술 더하기가 수행됩니다.

더하기와 달리 빼기 연산자에는 오버로드 된 동작이 없으므로 toNumeric먼저를 사용하여 기본 형식으로 변환 한 각 피연산자에 대해 호출 합니다 ToPrimitive.

그래서:

 1  +  1   //  2                 
'1' +  1   // '11'   Both already primitives, RHS converted to string, '1' + '1',   '11'
 1  + [2]  // '12'   [2].valueOf() returns an object, so `toString` fallback is used, 1 + String([2]), '1' + '2', 12
 1  + {}   // '1[object Object]'    {}.valueOf() is not a primitive, so toString fallback used, String(1) + String({}), '1' + '[object Object]', '1[object Object]'
 2  - {}   // NaN    {}.valueOf() is not a primitive, so toString fallback used => 2 - Number('[object Object]'), NaN
+'a'       // NaN    `ToPrimitive` passed 'number' hint), Number('a'), NaN
+''        // 0      `ToPrimitive` passed 'number' hint), Number(''), 0
+'-1'      // -1     `ToPrimitive` passed 'number' hint), Number('-1'), -1
+{}        // NaN    `ToPrimitive` passed 'number' hint', `valueOf` returns an object, so falls back to `toString`, Number('[Object object]'), NaN
 1 + 'a'   // '1a'    Both are primitives, one is a string, String(1) + 'a'
 1 + {}    // '1[object Object]'    One primitive, one object, `ToPrimitive` passed no hint, meaning conversion to string will occur, one of the operands is now a string, String(1) + String({}), `1[object Object]`
[] + []    // ''     Two objects, `ToPrimitive` passed no hint, String([]) + String([]), '' (empty string)
 1 - 'a'   // NaN    Both are primitives, one is a string, `ToPrimitive` passed 'number' hint, 1-Number('a'), 1-NaN, NaN
 1 - {}    // NaN    One primitive, one is an object, `ToPrimitive` passed 'number' hint, `valueOf` returns object, so falls back to `toString`, 1-Number([object Object]), 1-NaN, NaN
[] - []    // 0      Two objects, `ToPrimitive` passed 'number' hint => `valueOf` returns array instance, so falls back to `toString`, Number('')-Number(''), 0-0, 0

기본 힌트가 '숫자'가 아닌 '문자열'로 간주되는 Date기본 @@toPrimitive메서드 를 재정의하는 유일한 내장 함수 라는 점에서 intrinsic 개체는 고유 합니다. 이를 갖는 이유 Date는 프로그래머의 편의를 위해 인스턴스가 기본적으로 숫자 값 대신 읽을 수있는 문자열로 변환되도록 하기 위함입니다 . 를 @@toPrimitive사용하여 자신의 개체에서 재정의 할 수 있습니다 Symbol.toPrimitive.

다음 그리드는 추상 같음 연산자 ( ==) ( 소스 )에 대한 강제 결과를 보여줍니다 .

여기에 이미지 설명 입력

참고 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.