Object to primitive conversion
Conversion procedure
In JavaScript, object operands in operations, e.g. obj1 + 2
or obj1 == obj2
, need to be converted to primitives
before the operation can be carried out. Also functions and methods may require primitive arguments.
JS coerces objects into conversion to primitives via the object's inherited methods valueOf()
and toString()
or with the help of a Symbol.toPrimitive
property.
You rarely need to invoke these methods yourself; JavaScript automatically invokes them during type coercion.
All objects inherit the methods valueOf()
and toString()
from the Object.prototype
.
Unless overridden, by default, method valueOf()
returns the object itself and
method toString()
returns the string '[object Object]'.
When the object is a primitive wrapper object,
method valueOf()
returns the primitive (in the corresponding type),
and toString()
returns the primitive converted to a string.
// Default returns from the inherited Object.prototype methods:
const obj = { name:"John", age:26 }
console.log(obj.toString()); // logs: '[object Object]'
console.log(obj.valueOf()); // logs: { name: "John", age: 26 }
// Default returns for wrapper objects:
let num = new Number('15');
console.log(num); // logs: { 15 } // a number object
console.log(num.toString()); // logs: '15' // a string
console.log(num.valueOf()); // logs: 15 // a number
num = 15;
console.log(num.toString()); // logs: '15'
console.log(num.valueOf()); // logs: 15
let str = 'hello';
console.log(str.valueOf()); // logs: 'hello'
let bool = true;
console.log(bool.toString()); // logs: 'true'
console.log(bool.valueOf()); // logs: true
For every object you can override the default valueOf()
and/or toString()
methods by your own custom
valueOf()
and/or toString()
methods.
The customized function should not take any arguments, since arguments will not be passed when called during type conversion.
function Country(name, population) {
this.name = name;
this.population = population;
this.toString = function() {
return `country: ${this.name}`;
}
this.valueOf = function() {
return this.population;
}
};
const USA = new Country('United States of America', 350000000);
console.log(USA.toString()); // logs: 'country: United States of America' // instead of default '[object Object]'.
console.log(USA.valueOf()); // logs: 350000000 // instead of default returning the object itself.
When JS coerces objects to primitives, JS automatically calls the object's
valueOf()
or toString()
method (or uses the Symbol.toPrimitive
property which will be discussed later).
But which one does it call?
Where JS expects some specific primitive data type it conveys a "hint" for the conversion to the object. Based on this hint
one of the methods valueOf()
and toString()
is invoked.
The hint can be "number", "string" or "default". Numeric conversion (hint is "number") happens in mathematical contexts such as in arithmetic operations (except addition), greater than/less than operations or mathematical functions. String conversion (hint is "string") happens in text contexts. The default hint is conveyed when the hint is not "string" or "number". In non-strict equalities and when operands are added (arithmetic addition or a string concatenation) the hint is "default".
Object conversion to a primitive roughly follows the following procedure:
-
In a boolean context, objects
(truthy values) are always converted to boolean primitive
true
. The conversion procedure does not involve anything more. There is no hint conveyed. - In a non-boolean context:
-
If the
Symbol.toPrimitive
property exists, JavaScript tries to covert the object according to this property. IfSymbol.toPrimitive
fails to return a primitive, it will throw aTypeError
exception. - If the
Symbol.toPrimitive
property does not exist, JavaScript tries conversion via methodsvalueOf()
ortoString()
.-
If the hint is "string", JavaScript tries conversion via
toString()
first.If the hint is "number" or "default"[1], JavaScript tries conversion via
valueOf()
first. - If the first tried method does not return a primitive, it tries conversion via the other method.
- If the other method also fails to return a primitive, the statement trows a
TypeError
exception.
-
-
If the
[1]: If the hint is "default" for a Date
object, JS tries conversion via toString()
first.
After an object is converted to a primitive by following the procedure described above, this primitive may still not be the appropriate primitive type to carry out the operation or function. It may need another conversion, now from one primitive data type to another.
const num = new Number('5');
console.log(typeof num); // logs: "object"
console.log(num**2); // logs: 25 // hint is "number", so method valueOf() returns the primitive value; the number 25 in this case.
// Objects in logical contexts always coerce to boolean primitive "true".
// Empty array [] is an object.
console.log([] && true); // logs: true.
if ([]) { console.log("This gets returned") } // logs: "This gets returned".
let obj = {
name: "Jane Doe",
score: 99,
toString() {
return [this.name,this.score]; // returns an array
}
}
// Next: hint is "string", but both toString() and valueOf() return an object and not a primitive.
console.log(`${obj}`); // trows TypeError: can't convert obj to string
let obj = {
name: "Jane Doe",
score: 99,
toString() {
return `Your name: ${this.name}`;
}
}
// Next: The hint for converting obj is "number" so valueOf() is tried first.
// valueOf() returns the Object.prototype.valueOf() default, which is the object itself and not a primitive.
// So toString() is tried next, which returns a string primitive.
// Then it coerces this string primitive to a number to be able to carry out the subtraction.
// The string primitive converts to NaN (Not a Number).
console.log(obj - 5); // logs: NaN // NaN - 5 = NaN
obj.valueOf = function () { return this.score; } // custom method valueOf() is added to obj.
window.alert(obj); // logs: Your name: Jane Doe // hint is "string" because JS expects a string argument for window.alert();
console.log(obj - 5); // logs: 94 // hint is "number"
console.log(obj + 3); // logs: 102 // hint is "default"
// Next: obj is converted by valueOf to a number, because the hint is "default".
// Then this primitive number is converted to a string because a string is expected in the concatenation.
console.log(obj + " points"); // logs: "99 points"
// Next: strict equality operators do not coerce into type conversion.
console.log(obj == 99); // logs: true // hint is "default"
console.log(obj === 99); // logs: false // no hint, obj is not converted"
let obj = { toString() { return "2.1"; } }; // toString() returns a primitive string "2.1".
// obj.valueOf() does not convert to a primitive:
console.log(`The result is: ${obj * 2}`); // logs: "The result is: 4.2" // obj coerced to string, coerced to number
console.log(`The result is: ${obj + 2}`); // logs: "The result is: 2.12" // obj coerced to string, concatenated with "2"
obj.valueOf = function() { return 2.1; }
console.log(`The result is: ${obj * 2}`); // logs: "The result is: 4.2"
console.log(`The result is: ${obj + 2}`); // logs: "The result is: 4.1" // hint is "default"
Symbol.toPrimitive
ECMAScript 6 introduced a well-known symbol
Symbol.toPrimitive
to be used as
a key for a method that is called to convert an object to a corresponding primitive value.
The method is called with an argument hint
.
For each hint
("number", "string" and "default") you can provide a custom primitive value return.
The default return for hint "number" is NaN
, the default return for hints "string" and "default" is undefined
.
Unlike valueOf()
and toString()
,
if Symbol.toPrimitive
returns an object, it will throw a TypeError
.
When the Symbol.toPrimitive
method is available, methods valueOf()
and toString()
will not be used for conversion.
const obj = {
count: 313,
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return this.count;
}
return null;
}
}
console.log(+obj); // logs: 313 // hint is "number"
console.log(`${obj}`); // logs: "null" // hint is "string"
console.log(obj + 'hello'); // logs: "nullhello" // hint is "default"
console.log(obj[Symbol.toPrimitive]("number")); // logs: 313
// change method Symbol.toPrimitive:
obj[Symbol.toPrimitive] = function(hint) {
if (hint === 'number') {
return 5;
}
if (hint === 'string') {
return "hello";
}
return "no primitive value";
};
console.log(+obj); // logs: 5 // hint is "number"
console.log(`${obj}`); // logs: "hello" // hint is "string"
console.log(obj == "no primitive value"); // logs: true // hint is "default"
console.log(obj === "no primitive value"); // logs: false // no hint, obj is not converted"
const obj = {
[Symbol.toPrimitive](hint) {}
}
console.log(+obj); // logs: NaN // hint is "number"
console.log(`${obj}`); // logs: undefined // hint is "string"
console.log(obj + 'hello'); // logs: "undefinedhello" // hint is "default"
console.log(obj + 5); // logs: NaN // hint is "default" (undefined + 5 = NaN)