valueOf() vs. toString() in Javascript

In Javascript every object has a valueOf() and toString() method. I would have thought that the toString() method got invoked whenever a string conversion is called for, but apparently it is trumped by valueOf().

For example, the code

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

will print

x=42
x=foo

This strikes me as backwards .. if x were a complex number, for example, I would want valueOf() to give me its magnitude, but whenever I wanted to convert to a string I would want something like "a+bi". And I wouldn't want to have to call toString() explicitly in contexts that implied a string.

Is this just the way it is?


Solution 1:

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 + b:
    pa = ToPrimitive(a)
    if(pa is string)
       return concat(pa, ToString(b))
    else
       return add(pa, ToNumber(b))

and this is what actually happens

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

That is, toString is applied to the result of valueOf, not to your original object.

For further reference, check out section 11.6.1 The Addition operator ( + ) in the ECMAScript Language Specification.


*When called in string context, ToPrimitive does invoke toString, but this is not the case here, because '+' doesn't enforce any type context.

Solution 2:

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 + 1; // 43
["x=", x].join(""); // "x=foo"

The toString function is not "trumped" by valueOf in general. The ECMAScript standard actually answers this question pretty well. Every object has a [[DefaultValue]] property, which is computed on-demand. When asking for this property, the interpreter also provides a "hint" for what sort of value it expects. If the hint is String, then toString is used before valueOf. But, if the hint is Number, then valueOf will be used first. Note that if only one is present, or it returns a non-primitive, it will usually call the other as the second choice.

The + operator always provides the hint Number, even if the first operand is a string value. Even though it asks x for its Number representation, since the first operand returns a string from [[DefaultValue]], it does string concatenation.

If you want to guarantee that toString is called for string concatenation, use an array and the .join("") method.

(ActionScript 3.0 slightly modifies the behavior of +, however. If either operand is a String, it will treat it as a string concatenation operator and use the hint String when it calls [[DefaultValue]]. So, in AS3, this example yields "foo, x=foo, foo=x, foo1, 43, x=foo".)

Solution 3:

TLDR

Type coercion, or implicit type conversion, enables weak typing and is used throughout JavaScript. Most operators (with the notable exception of the strict equality operators === and !==), and value checking operations (eg. if(value)...), will coerce values supplied to them, if the types of those values are not immediately compatible with the operation.

The precise mechanism used to coerce a value depends on the expression being evaluated. In the question, the addition operator is being used.

The addition operator will first ensure both operands are primitives, which, in this case, involves calling the valueOf method. The toString method is not called in this instance because the overridden valueOf method on object x returns a primitive value.

Then, because one of the operands in the question is a string, both operands are converted to strings. This process uses the abstract, internal operation ToString (note: capitalized), and is distinct from the toString method on the object (or its prototype chain).

Finally, the resulting strings are concatenated.

Details

On the prototype of every constructor function object corresponding to every language type in JavaScript (ie. Number, BigInt, String, Boolean, Symbol, and Object), there are two methods: valueOf and toString.

The purpose of valueOf is to retrieve the primitive value associated with an object (if it has one). If an object does not have an underlying primitive value, then the object is simply returned.

If valueOf is invoked against a primitive, then the primitive is auto-boxed in the normal way, and the underlying primitive value returned. Note that for strings, the underlying primitive value (ie. the value returned by valueOf) is the string representation itself.

The following code shows that the valueOf method returns the underlying primitive value from a wrapper object, and it shows how unmodified object instances that do not correspond to primitives, have no primitive value to return, so they simply return themselves.

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

The purpose of toString, on the other hand, is return a string representation of an object.

For example:

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

For most operations, JavaScript will silently attempt to convert one or more operand(s) to the required type. This behavior was chosen to make JavaScript easier to use. JavaScript initially did not have exceptions, and this may have also played a role in this design decision. This kind of implicit type conversion is called type coercion, and it is the basis of JavaScript's loose (weak) type system. The complicated rules behind this behavior are intended to move the complexity of typecasting into the language itself, and out of your code.

During the coercive process, there are two modes of conversion that can occur:

  1. Conversion of an object to a primitive (which might involve a type conversion itself), and
  2. Direct conversion to a specific type instance, using a constructor function object of one of the primitive types (ie. Number(), Boolean(), String() etc.)

Conversion To A Primitive

When attempting to convert non-primitive types to primitives to be operated upon, the abstract operation ToPrimitive is called with an optional "hint" of 'number', or 'string'. If the hint is omitted, the default hint is 'number' (unless the @@toPrimitive method has been overridden). If the hint is 'string', then toString is tried first, and valueOf second if toString did not return a primitive. Else, vice-versa. The hint depends on the operation requesting the conversion.

The addition operator supplies no hint, so valueOf is tried first. The subtraction operator supplies a hint of 'number', so valueOf is tried first. The only situations I can find in the spec in which the hint is 'string' are:

  1. Object#toString
  2. The abstract operation ToPropertyKey, which converts an argument into a value that may be used as a property key

Direct Type Conversion

Each operator has its own rules for completing their operation. The addition operator will first use ToPrimitive to ensure each operand is a primitive; then, if either operand is a string, it will then deliberately invoke the abstract operation ToString on each operand, to deliver the string concatenation behavior we expect with strings. If, after the ToPrimitive step, both operands are not strings, then arithmetic addition is performed.

Unlike addition, the subtraction operator does not have overloaded behavior, and so will invoke toNumeric on each operand having first converted them to primitives using ToPrimitive.

So:

 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

Note that the Date intrinsic object is unique, in that it is the only intrinsic to override the default @@toPrimitive method, in which the default hint is presumed to be 'string' (rather than 'number'). The reason for having this, is to have Date instances translate to readable strings by default, instead of their numeric value, for the convenience of the programmer. You can override @@toPrimitive in your own objects using Symbol.toPrimitive.

The following grid shows the coercion results for the abstract equality operator (==) (source):

enter image description here

See also.