How Symbol.toPrimitive Works in JavaScript
Symbol.toPrimitive is a built-in JavaScript symbol that
allows developers to customize how an object is converted into a
primitive value (such as a string, number, or boolean). By defining a
method with this symbol on an object, you override the default
object-to-primitive coercion mechanisms, giving you complete control
over implicit and explicit type conversions based on the context in
which the object is used.
The Conversion Hints
When JavaScript needs to convert an object to a primitive, it passes
a hint argument to the [Symbol.toPrimitive]
method. There are three possible hint values:
"number": Used when an operation explicitly or implicitly expects a numeric value. Examples include mathematical operators like subtraction (-), multiplication (*), bitwise operators, unary plus (+obj), or functions likeMath.max()."string": Used when an operation expects a string. Examples includeString(obj), template literals (`${obj}`), and object keys in property access."default": Used when the engine is unsure what type to expect. The most common examples are the binary addition operator (+), which can perform either string concatenation or numeric addition, and the loose equality operator (==).
Implementation Example
You can implement Symbol.toPrimitive directly on an
object or a class prototype. The method takes hint as a
parameter and must return a primitive value.
const user = {
name: "Alice",
age: 30,
[Symbol.toPrimitive](hint) {
switch (hint) {
case "string":
return `User: ${this.name}`;
case "number":
return this.age;
case "default":
return `${this.name} (${this.age})`;
default:
throw new TypeError("Invalid hint");
}
}
};
// Coercion behaviors:
console.log(String(user)); // "User: Alice" (hint: "string")
console.log(+user); // 30 (hint: "number")
console.log(user + 5); // "Alice (30)5" (hint: "default")Precedence Over
valueOf and toString
In older JavaScript implementations, object-to-primitive conversion
relied on defining separate valueOf() and
toString() methods.
When [Symbol.toPrimitive] is defined on an object, it
takes highest precedence. The JavaScript engine will execute
[Symbol.toPrimitive] and completely bypass
valueOf and toString. This provides a single,
unified interface to handle all primitive conversion scenarios cleanly
and predictably.