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.
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
- Unique Identifiers: Symbols are guaranteed to be unique. No two symbols are ever the same.
- 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". - 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.
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.
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
- Video: Symbols and Well-Known Identifiers
- MDN: Symbol
- Concept: Well-known Symbols
Keep reading