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.
#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!