DEV Community

JS Bits Bill
JS Bits Bill

Posted on • Edited on

Symbols Are Your Friend Part I: A Gentle Introduction

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 🍔:

  1. Boolean
  2. BigInt
  3. undefined
  4. Number
  5. String
  6. 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);
Enter fullscreen mode Exit fullscreen mode

Note that these return values are not strings but rather symbols:

console.log(typeof s2); // Logs "symbol"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!)
Enter fullscreen mode Exit fullscreen mode

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!'
Enter fullscreen mode Exit fullscreen mode

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'
};
Enter fullscreen mode Exit fullscreen mode
// service.js

// Later in our app, some service overwrites the id 😠
character.id = 999;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
timmybytes profile image
Timothy Merritt

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 😂

Collapse
 
js_bits_bill profile image
JS Bits Bill

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.

Collapse
 
timmybytes profile image
Timothy Merritt

Totally. null has some definite weirdness. I wrote about it and other primitives (but not Symbol!) here.

Collapse
 
shadowtime2000 profile image
shadowtime2000 • Edited

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 july

Collapse
 
lexlohr profile image
Alex Lohr

console.log is just parsing the output of the .toString() method.

Collapse
 
oliverobenland profile image
Oliver Obenland

He joined last year in September. No time traveling involved 😉