In this section, we will learn what the ToPrimitive operation is and how it works in JavaScript.
What is ToPrimitive Operation?
The ToPrimitive is an abstract operation. We say abstract because each JavaScript engine might have implemented the body of this operation in a slightly different way. But at its core, when an object is involved as an operand in an operation that needs primitive values; the execution engine will run the ToPrimitive operation to coerce (convert) the target object into a primitive value.
Depending on the type of operation, the result of ToPrimitive operation on an object will either return a `String` type value or a `Number` type value.
How Does ToPrimitive Operation Work in JavaScript?
In JavaScript, we know that objects at the end are linked to the prototype object of the `Object` function.
In this prototype object, there are two methods:
- toString(): this method will be used when an object is involved in an operation that it needs a value of type String.
- valueOf(): this method will be used when an object is involved in an operation that it needs a value of type Number.
JavaScript Object toString() method
As mentioned before, the first method (toString()) will be called by the JavaScript execution engine when we invoke the name of an object in an operation that needs String type of value.
So let’s see this in practice:
Example: invoking JavaScript Object toString() method
const obj = { firstName: "John" } console.log(String(obj));
Output:
[object Object]
In the statement:
console.log(String(obj));
We used the `String` constructor function. This constructor will take an argument and convert that value into String data type if the value is not already of type String and finally creates an object that represents the string value.
So because we used the object `obj`, the execution engine will invoke the `toString()` method and use the return value of this method as the argument for the `String()` constructor.
By default, the return value of calling the `toString()` method on an object like the one in the example will return a weird value which is `[object Object]`. But we’re not limited to this value and we can override the `toString()` method in the target object and define specifically what value should be returned.
Note: overriding a method in an object is the idea of rewriting the same method (using the same name) but setting a different body for that method in the target object. So when we say override the toString() method in the body of an object, that means rewrite that method in that object.
Example: object toString() method in JavaScript
const obj = { firstName: "John", toString(){ return this.firstName; } } console.log(String(obj));
Output: John
As you can see now, the execution engine used the `toString()` method that we defined in the body of the object.
So again, when there’s an object value in an operation that needs String value instead, the execution engine will call the `toString()` method of that object to get the String value and use that in the target operation.
JavaScript Object valueOf() Method
Now if there’s an object value in an operation that needs a value of type Number instead, this time the execution engine will use the `valueOf()` method of the target object to get a value of type Number. After that, this new value will be used in the target operation.
Basically, the `valueOf()` method is used to return a Number value. But if we don’t override this method in each object that might involve in such operation, by default the result of calling this `valueOf()` method won’t return a number! The result is actually rather ambiguous!
By default, when the execution engine calls the `valueOf()` method, if the returned value cannot be used for the target operation, the `toString()` method will be called instead to see if its result can be used in the operation instead. At the end, if none of the returned results from these two methods can be used, we will get the Type Error.
Example: invoking JavaScript Object valueOf() method
const obj = { firstName: "John", toString(){ return "10"; } } console.log(Number(obj));
Output:
10
Take a look at this statement:
console.log(Number(obj));
Here, we used the `Number` constructor function. This constructor is used to create an object that represents a number. We put the number as the argument of the function.
But here, instead of a number value, we set an object. So the execution engine first will call the `valueOf()` method to see if it gets a value of type Number or even a value that can be coerced into Number. But the result is not what the engine can accept. For this reason, it will call the `toString()` method of that object instead.
Here the result of the call to the `toString()` method is a value of type String which is `10`. So the execution engine will coerce one more time to see if this result can be converted into a number type or not. Fortunately, the value after the coercion becomes the number 10 and so the operation can proceed successfully.
Note: We could override the `valueOf()` method in the `obj` object and return a number value from that method as well.
Example: overriding valueOf() method in JavaScript object
const obj = { firstName: "John", age: 50, toString(){ return "10"; }, valueOf(){ return this.age; } } console.log(Number(obj));
Output: 50
Besides using constructors, there are other types of operations that if we put an object as the operand, the execution engine might end up calling either of the two methods mentioned above.
For example, using objects as the operands in a multiplication, subtraction, division and other mathematical operations will involve the call to either of these methods.
Example: JavaScript ToPrimitive operation on objects
const obj = { firstName: "John", age: 50, toString(){ return "10"; }, valueOf(){ return this.age; } } const res = obj - 10; console.log(res);
Output: 40
In the statement:
const res = obj - 10;
The type of operation is subtraction and this means the operands should be of type Number. So the execution engine will call the `valueOf()` method in the `obj` object to get its value and use it in this operation.
The return value of the `valueOf()` method is 50 and so the result of the `50 – 10` expression becomes 40. And that’s how we got the value 40 on the browser’s console.
There’s a good chance that you might never use such an operation as the one in the example above, but it’s good to know what will happen if we try to do so.
Note: In a program if needed, we can explicitly call both `toString()` and `valueOf()` methods like `obj.toString()` or `obj.value()`. There’s nothing wrong with that either.