DEV Community

Amin
Amin

Posted on

Symbols & enumerations in JavaScript

Symbols in JavaScript have been added in recent versions of ECMAScript and are yet to be understood by many developers.

Today's article is about understanding and finding a use-case for them.

Let's say we have a game state that is handled by a plain object.

const GameState = {
  Pristine: "PRISTINE",
  Playing: "PLAYING",
  Finished: "FINISHED"
}
Enter fullscreen mode Exit fullscreen mode

Our game can, for now, take three states : it is either new (GameState.Pristine), ongoing (GameState.Playing) or finished (GameState.Finished).

Let's now log a message to the console based on the state of a game that has been created.

const gameState = GameState.Playing;

switch (gameState) {
  case GameState.Pristine:
    console.log("Welcome.");
    break;

  case GameState.Playing:
    console.log("Game in progress.");
    break;

  case GameState.Finished:
    console.log("Game finished.");
    break;
}
Enter fullscreen mode Exit fullscreen mode

Based on the value of our gameState variable that holds a variant of our GameState object, we can output something in the console of our users.

If we would like to add a new feature for our users to pause the game, we would add a new state to our GameState.

const GameState = {
  Pristine: "PRISTINE",
  Playing: "PLAYING",
  Paused: "PLAYING",
  Finished: "FINISHED"
}
Enter fullscreen mode Exit fullscreen mode

Those of you that have spotted the mistake right away get one special bonus point. If you do not understand why I'm saying this, go back to the new definition of our GameState.

Notice anything? We made a mistake by copy/pasting the Playing state for defining our new Paused state.

  Paused: "PLAYING",
Enter fullscreen mode Exit fullscreen mode

Now, of course if we spotted the mistake we could correct the value of our Paused property. But we are all humans, and mistakes happens quick.

Now let's say we didn't spot the mistake. This means that if we want to react to our new state and have a message, we would get the wrong message instead.

const gameState = GameState.Paused;

switch (gameState) {
  case GameState.Pristine:
    console.log("Welcome.");
    break;

  case GameState.Playing:
    console.log("Game in progress.");
    break;

  case GameState.Paused:
    console.log("Game is paused.");
    break;

  case GameState.Finished:
    console.log("Game finished.");
    break;
}
Enter fullscreen mode Exit fullscreen mode

With this code, we would have the following message output in the console even though our state is set to GameState.Paused.

Game in progress.
Enter fullscreen mode Exit fullscreen mode

Now, this is a simple console, imagine on a larger scale of a real-world application. The consequence could be terrible for the user interface.

We could, instead of using a String, use a Symbol for the value of our state.

When you create a symbol, it always create a new value that is unique accross your entire script. It is not possible to create two symbols that may have the same value.

This means this code will always output false in the console.

console.log(Symbol() === Symbol());
// false
Enter fullscreen mode Exit fullscreen mode

The Symbol() is a constructor for creating a new symbol. It can take an argument which is a description, for debugging purposes.

const myUniqueSymbol = Symbol("MY_SYMBOL");
Enter fullscreen mode Exit fullscreen mode

And no, you cannot create two identical symbols by having two symbols with the same description. Again, the description is only here for debugging purposes, this is not the value of a symbol.

console.log(Symbol("MY_SYMBOL") === Symbol("MY_SYMBOL"));
// false
Enter fullscreen mode Exit fullscreen mode

A symbol has no interesting value, meaning a scalar or a composed value. This is really and solely a data structure for unique things.

console.log(Symbol("TEST"));
// Symbol(TEST)
Enter fullscreen mode Exit fullscreen mode

And its type is not a string like we could expect since we provided a description, but a symbol.

console.log(typeof Symbol("TEST"));
// symbol
Enter fullscreen mode Exit fullscreen mode

This makes it awesome for things that require a unique value, like an enumeration. We don't really care about the value of a symbol, but we care about unique values for our enumeration.

const GameState = {
  Pristine: Symbol(),
  Playing: Symbol(),
  Paused: Symbol(),
  Finished: Symbol()
}
Enter fullscreen mode Exit fullscreen mode

Now, if I were to copy/paste a state to create a new one, I'm guaranteed to have one unique state, this help us prevent silly mistake like the one I made earlier.

const GameState = {
  Pristine: Symbol(),
  Playing: Symbol(),
  Paused: Symbol(),
  Finished: Symbol(),
  Canceled: Symbol()
}

const gameState = GameState.Canceled;

switch (gameState) {
  case GameState.Pristine:
    console.log("Welcome.");
    break;

  case GameState.Playing:
    console.log("Game in progress.");
    break;

  case GameState.Paused:
    console.log("Game is paused.");
    break;

  case GameState.Finished:
    console.log("Game finished.");
    break;

  case GameState.Canceled:
    console.log("Game canceled.");
    break;
}
Enter fullscreen mode Exit fullscreen mode

As you can see, we added a new Canceled state, and if you try to run this code, you'll notice that it works just fine. Try to play with it and change the state to see it in action.

However, if we were to debug this symbol, we would have a hard time trying to figure out which state does it represent.

const GameState = {
  Pristine: Symbol(),
  Playing: Symbol(),
  Paused: Symbol(),
  Finished: Symbol(),
  Canceled: Symbol()
}

const gameState = GameState.Canceled;

console.log(gameState);
// Symbol()
Enter fullscreen mode Exit fullscreen mode

This Symbol() output is not very useful, hence the need for a description, which can be handy in development.

const GameState = {
  Pristine: Symbol("PRISTINE"),
  Playing: Symbol("PLAYING"),
  Paused: Symbol("PAUSED"),
  Finished: Symbol("FINISHED"),
  Canceled: Symbol("CANCELED")
}

const gameState = GameState.Canceled;

console.log(gameState);
// Symbol(CANCELED)
Enter fullscreen mode Exit fullscreen mode

And here is the final result.

const GameState = {
  Pristine: Symbol("PRISTINE"),
  Playing: Symbol("PLAYING"),
  Paused: Symbol("PAUSED"),
  Finished: Symbol("FINISHED"),
  Canceled: Symbol("CANCELED")
}

const gameState = GameState.Canceled;

switch (gameState) {
  case GameState.Pristine:
    console.log("Welcome.");
    break;

  case GameState.Playing:
    console.log("Game in progress.");
    break;

  case GameState.Paused:
    console.log("Game is paused.");
    break;

  case GameState.Finished:
    console.log("Game finished.");
    break;

  case GameState.Canceled:
    console.log("Game canceled.");
    break;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Now you know what a symbol is, and you have a use-case for implementing symbols in your application. This will let you reduce the vector of mistakes that you can make.

Since creating enumerations will involve a lot of code repetition (only the keys will remains the same), we could create a function that will act as a factory for producing enumerations on demand.

This is left as an exercise for the reader, and anything you want to share is always welcome in the comment section.

Have fun!

Oldest comments (0)