JS Bits with Bill
JS Bits with Bill

JS Bits with Bill

Symbols Are Your Friend Part I: A Gentle Introduction

Symbols Are Your Friend Part I: A Gentle Introduction

JS Bits with Bill's photo
JS Bits with Bill

Published on Oct 7, 2020

4 min read

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);

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!

 
Share this