2 min read

ES6's Unique Symbols

by Vihan Bhargava
  • JavaScript
  • ES6

ES6 brings a pretty nifty feature— Symbols. Symbols may seem like a small feature but they have such a wide array of uses which can really help make your JavaScript safer and easier to understand.

ES6 brings a pretty nifty feature— Symbols. Symbols may seem like a small feature but they have such a wide array of uses. In a language I’ve been developing, I’ve been making heavy use of symbols. They do everything from private properties to enums.

No… not those
cymbals…
No… not those cymbals…

#What are Symbols?

Symbols are a new type in JavaScript ES6. They don’t have any literal form but they can be made with the Symbol constructor. Essentially they are unique values. When I call Symbol I get a new and unique symbol every time.


Additionally, I can pass an argument to Symbol, this argument is the description of the symbol. I personally usually make the description the same as the variable I’m assigning it to (e.g. const A = Symbol('A');).

What does this name do? Well it’s just for debugging. If I were to do console.log(Symbol()) it would output Symbol(), I wouldn’t have any idea what symbol was being logged. If I name it, then I know exactly what symbol is being talked about console.log(Symbol('MyFirstSymbol')) outputs Symbol(MyFirstSymbol).

#Example Symbol Declaration

Here’s an example of a symbol:

const MySymbol = Symbol('MySymbol');

assert(
  MySymbol === MySymbol // true
);
assert(
  MySymbol === Symbol('MySymbol') // false
);

As you can see, a symbol’s description has no semantic meaning, it exists solely for debugging.

#Symbols work throughout imports

Symbols can be exported and still maintain their value, this makes them very useful. Here’s an example for an assert function.

states.js:

export default {
  SUCCESS: Symbol('SUCCESS'),
  ERRROR:  Symbol('ERROR')
}

assert.js:

import states from './states';

export default (condition) => condition
  ? states.SUCCESS
  : states.ERROR

test.js:

import assert from './assert';
import states from './states';

if (assert( 2 === 2 ) === states.SUCCESS) {
  console.log('All good');
} else {
  console.log('Maths is borked');
}

#So what are the uses?

Unique values? These have numerous uses. I’ll go over a couple, including:

  • Un-collidable Property Names
  • Enumerations
  • Private Properties

#Property Names that do not collide

This is most probably the most obvious use of symbols. Essentially, if you want to add a private properties. For example, if I wanted to add a property to a fade-in-out library I was writing, before symbols I might have something like this:

Element.prototype.FADE_IN_OUT_LIB_DO_NOT_USE_PLEASE

which, well… just looks dumb. Symbols can solve this problem:

const FADE_STATE = Symbol('FADE_STATE');
Element.prototype[FADE_STATE] = 1;

now, Element.prototype[FADE_STATE] is only available when you have access to the FADE_STATE variable. By logging Element.prototype, the user may be able to see FADE_STATE and its value, but only be able to reference it through some hacky code like:

Element.prototype[
  Object.getOwnPropertySymbols(Element.prototype)
  .filter(sym =>
    sym.toString() === "Symbol(FADE_STATE)"
  )[0]
]

but if the user is using Object.getOwnPropertySymbols, they are almost definitely attempting to retrieve private properties. If you’re worried about the user finding passwords because you’re storing them in a global object: why the hell are you storing passwords locally.

#Private Properties

I can also use symbols for private properties. Here’s an example:

const Foo = Symbol('Foo');

class MyClass {
  constructor(val) {
    this[Foo] = val;
  }

  read() {
    return this[Foo];
  }
}

This way, I have a private variable Foo, which I cannot access:

let Foo = new MyClass("abc123");
console.log( this[Symbol('Foo')] ); // undefined
console.log( Foo.read() ); // "abc123"

#Enumerations

This is a pretty simple idea but compared to previous implementations of enumerations in JavaScript, this combined with the behavior described in: “Symbols work throughout imports” (a previous section at the top), make these great ways to represent states. Here’s an example for a tokenizer:

const TYPES = {
  STRING: Symbol('String'),
  NUMBER: Symbol('Number'),
  ARRAY : Symbol('Array'),
  BOOL  : Symbol('Bool')
};

Now, if I want to specify what type a variable is, I can slap on one of these enum values.


Here’s a simple enum constructor using symbols:

function Enum(...vals) {
  return vals.reduce(
    (obj, val) => (obj[val] = Symbol(val), obj)
  , {});
}

Now if I go ahead and do:

const TYPES = Enum(
  "STRING",
  "NUMBER",
  "ARRAY",
  "BOOL"
);

this will return a value which is equivalent to the above TYPES object.


What can you do with this? Well just about everything as enums that every other language has! Symbols are always unique so you never have to worry about problems due to using other types such as numbers for enums.

#Conclusion

Symbols are a very neat feature in ES6. They can be used for all sorts of features requiring unique values and due to their ability to be passed by reference, they can be used for passing data known by or associated to a unique “item”, rather than a specific literal. I probably haven’t covered every possibly use of Symbols, they are probably many more cool uses of Symbols.

——

I hope you enjoyed reading this post and learned something. If you have your own cool use of Symbols, feel free to leave a comment!