The concept of the Symbol can trip up many a developer. In this article, I'll attempt to demystify them and describe what they are.
To start: Symbols are a new primitive data type in JavaScript. If you haven't memorized the 6 primitives in JS, I use the mnemonic device acronym of BBUNSS 🍔:
- Boolean
- BigInt
- undefined
- Number
- String
- Symbol
The Symbol data type is simply a unique value. Unique values are useful to avoid name conflicts involving variables and object properties.
To create a new symbol we simply call the global Symbol
function, optionally passing in a descriptor string:
const s1 = Symbol();
console.log(s1); // logs Symbol()
const s2 = Symbol('abc');
console.log(s2); // logs Symbol(abc);
Note that these return values are not strings but rather symbols:
console.log(typeof s2); // Logs "symbol"
Another gotcha' with Symbol, is that every time you create one, it is totally unique from any other symbol you created before. This demonstrates that the string you pass into the function is not being coerced into a symbol - it's simply a label that can be used for clarity or debugging:
Symbol('abc') === Symbol('abc'); // false
While the return values of Symbol('abc')
and Symbol('abc')
print out exactly the same in the code, this doesn't mean that they're the same - these are unique entities.
So why would we want to create these unique values? Their main purpose is to function as an identifier for object properties.
But wait. We already use string-based keys to identify object properties. What benefit would symbols provide?
Consider the following function getRace()
that takes a string of your favorite Dragon Ball Z character and uses a switch statement to return their race:
const GOKU = 'Goku';
const PICCOLO = 'Piccolo';
const BULMA = 'Bulma';
const KRILLIN = 'Piccolo'; // <- Oops, someone messed up!
function getRace(character) {
switch (character) {
case GOKU:
return 'Saiyan';
case PICCOLO:
return 'Namekian';
case BULMA:
return 'Human';
default:
console.log('No race found!');
}
}
getRace(PICCOLO); // Returns 'Namekian'
getRace(KRILLIN); // Returns 'Namekian' (D'oh!)
Here we intended for only one unique "Piccolo" character to be created. But the variable KRILLIN
was also created and set to the same value. So when getRace(KRILLIN)
is called, our function returns 'Namekian'
because of this conflict. With symbols, we can create 100% unique identifiers:
const GOKU = Symbol('Goku');
const PICCOLO = Symbol('Piccolo');
const BULMA = Symbol('Bulma');
const KRILLIN = 'Piccolo';
function getRace(character) {
switch (character) {
case GOKU:
return 'Saiyan';
case PICCOLO:
return 'Namekian';
case BULMA:
return 'Human';
default:
console.log('No race found!');
}
}
getRace(PICCOLO); // Returns 'Namekian'
getRace(KRILLIN); // Logs 'No race found!'
Now we're checking for those exact unique symbols inside that switch statement instead of non-unique strings to get a more expected result.
Let's look at another example:
// app.js
// Create character record
const character = {
id: 123,
name: 'Goku',
race: 'Saiyan'
};
// service.js
// Later in our app, some service overwrites the id 😠
character.id = 999;
Since we used a regular string-based key to create the id property, any other place in our app can have code that could access and modify the property's value. That is not always desirable.
Let's use a symbol for the id instead:
// app.js
// Create character record w/ id symbol
const id = Symbol('id');
const character = {
[id]: 123, // Using [id] makes the key dynamic
name: 'Goku',
race: 'Saiyan'
};
console.log(character.id) // Logs undefined
console.log(character[id]); // Logs 123
Now the id can only be read or changed if we explicitly use the "id" symbol to access the property. Other parts of the app will not be able to access this property unless we also provide the symbol. This prevents clashes since we're not using a string for the property access.
You can see that the use of symbols can "harden" our logic in our code. There's plenty more to explore about symbols in another article, but hopefully this was a nice gentle introduction of their concept! 🐤
Check out more #JSBits at my blog, jsbits-yo.com. Or follow me on Twitter!
Top comments (6)
This is the most concise description for
Symbol
I’ve read so far, thank you. Also I will never forget primitives now thanks to BBUNSS 😂Thank you! I forgot to mention that BBUNSS covers the primitive data types but there's also null which MDN describes as a "Structure Root Primitive" which they consider sort of an exception.
Totally.
null
has some definite weirdness. I wrote about it and other primitives (but not Symbol!) here.I wonder how
console.log
is still able to access it. I have a question that really isn't about this post. I am just wondering why it says you join on september 9th when i have seen you do posts since julyconsole.log is just parsing the output of the .toString() method.
He joined last year in September. No time traveling involved 😉