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"
}
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;
}
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"
}
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",
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;
}
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.
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
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");
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
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)
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
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()
}
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;
}
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()
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)
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;
}
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!
Top comments (0)