valueOf() vs. toString() in Javascript
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".)
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.
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:
- Conversion of an object to a primitive (which might involve a type conversion itself), and
- 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:
Object#toString
- 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):
See also.