Symbols And Well Known Identifiers

Symbols are a unique data type used for semi-hidden properties. They are the key to unlocking the underlying features of JavaScript.

April 25, 20262 min read4 / 5

In the last part, we saw that hidden properties are the key to how JavaScript manages object-to-primitive math. But how do we add these labels to our own objects? We can't write them out directly (you can't type @@ in a property name), and we can't use regular strings because we might clash with existing code. The solution is the Symbol.

The Essentials

  1. Unique Identifiers: Symbols are guaranteed to be unique. No two symbols are ever the same.
  2. Backward Compatibility: Symbols allow JavaScript to add new features (like @@toPrimitive) without breaking 30 years of code that might have used a regular string like "toPrimitive".
  3. Well-Known Symbols: A built-in list of "known" symbols that JavaScript uses to control its internal behaviors.

The String Clash Problem

Imagine if JavaScript's designers decided to use a regular string for coercion rules. They might say, "From now on, if you have a property called toPrimitive, we will use it to convert your object."

This would be a disaster. Millions of developers over the last 30 years might have used the name toPrimitive for their own functions. Suddenly, their code would start behaving in unexpected ways as JavaScript automatically grabbed and ran their functions.

To avoid this, JavaScript uses a brand new data type that didn't exist until 2015: the Symbol.

Semi-Hidden Properties

Symbols are semi-hidden. If you loop through an object's properties, you won't find them. If you console log them, you won't even see their true value: just a stringified version of their type.

They are unique identifiers that we cannot write out directly. Instead, we refer to them by reference.

The "Well-Known" Symbols

JavaScript provides a built-in object called Symbol that contains labels for these internal features. One of them is Symbol.toPrimitive.

JavaScript
console.log(Symbol.toPrimitive); // Symbol(Symbol.toPrimitive)

Even though we can't "see" the underlying @@toPrimitive label, we can refer to it. This allows us to use square bracket notation to attach our own rules to an object.

JavaScript
const myObject = {}; const myCoercionRule = function() { return 105; }; // We use the reference to the symbol as our key myObject[Symbol.toPrimitive] = myCoercionRule;

Metaprogramming: Taking Control

By using these well-known symbols, we are engaging in metaprogramming. We are taking control of the underlying features of the programming language. We are overriding the default rules and making implicit behaviors explicit.

In the final part of this series, we'll see exactly how to implement this toPrimitive rule to make our objects behave exactly how we want them to.

Further Reading and Watching