Ecmascript 6 Features 8: Symbols
This week's ES6 post is about a rather strange (but useful) feature that I have only just discovered: Symbols. Before I explain to you how they work, be warned: I don't understand them fully myself yet.
Symbols are like unique keys and can be used as properties so you can use to ensure that you don't clash with any other property in an object. When creating one, you can optionally give it a 'name' so you know what it is - this can be any data type, but Symbol() will call .toString() on it first. Note that two symbols created with the same name aren't the same:
> sym1 = Symbol("starbeamrainbowlabs")
Symbol(starbeamrainbowlabs)
> sym2 = Symbol("starbeamrainbowlabs")
Symbol(starbeamrainbowlabs)
> sym1 == sym2
false
Strange, right?! This can be very useful though. You could use this to attach extra data to an object that you have been given by a library to identify it, for example, or you can use it to store something in a browser's localStorage without clashing with any other scripts running on the same domain (but this apparently doesn't persist across page reloads):
> localStorage[Symbol("bobs-rockets")] = "Some data";
"Some data"
> localStorage[Symbol("bobs-rockets")] = "Some data";
"Some data"
> localStorage
Storage { Symbol(bobs-rockets): "Some data", Symbol(bobs-rockets): "Some data" }
While this is cool, this raises the issue of retrieving the data we stored when we next load the page. If we try to create a new symbol with the same name that we used before, the browser returns undefined because the symbol we just created is not the same as the one we created the last time we loaded the page, as we discovered above.
Our next thought might be to iterate over the object and try to work out which one is ours:
> Object.keys(data).forEach(function(key) {
... console.log(`${key}: ${value}`);
... });
some-key: some-data
some-other-key: some-other-data
undefined
But weirdly, the Symbols we used don't show up! Object.keys() only returns an array of string based keys, and completely ignores the Symbols we created. This calls for a special function: Object.getOwnPropertySymbols(). This returns an array of all the Symbol based properties in an object. Then you can iterate over them like so:
> Object.getOwnPropertySymbols(data).forEach(function(key) {
... console.log(`${key.toString()}: ${value}`);
... });
Symbol(bobs-rockets): some-data
Symbol(bobs-rockets): some-data
undefined
Note the .toString() call here. If you try and convert a Symbol to a string, an exception is thrown:
TypeError: Cannot convert a Symbol value to a string
The same goes for if you accidentally use the new keyword when creating a Symbol:
> var sym = new Symbol();
TypeError: Symbol is not a constructor
There is an ever better way to recreate the same symbol across page loads though. You can use the Symbol.for() function:
> bobsSymbol = Symbol.for("bobs-rockets")
Symbol(bobs-rockets)
> bobsSymbol2 = Symbol.for("bobs-rockets")
Symbol(bobs-rockets)
> bobsSymbol == bobsSymbol2
true
> Symbol.for("bobs-rockets") == Symbol.for("bills-boosters")
false
This also allows two libraries to share the same symbol - but make sure that the string you pass to Symbol.for() is unique. This brings me onto the last thing I found out - you can reverse engineer a Symbol you obtained through Symbol.for() to work out the string used to create it with Symbol.keyFor():
> bobsSymbol = Symbol.for("bobs-rockets")
Symbol(bobs-rockets)
> Symbol.keyFor(bobsSymbol)
'bobs-rockets'
That concludes this ES6: Features post. To summarise, Symbols are unique keys that can be used to avoid conflicts in property names of objects. Every time you create one, you make a brand new Symbol that is completely different to any created before, except if Symbol.for() was used. Symbols created with Symbol.for() can be reverse-engineered to extract the string used to create it. Next time, I will probably look at well known symbols.