Explain Me Like I am Five: What are ES6 Symbols?
Tags: javascript, beginners, codenewbie
Published: 2020-09-10 07:54:49

# Introduction
`Symbol` is a primitive type(not an object) included in the ECMAScript 2015(aka, ES6). We are already familiar with the existing primitive types like, `Number`, `String` and, `Boolean`. Like these primitive types, Symbols are also created via a factory function,

```js
const sym = Symbol('Symbol Name');
```
Note, the parameter `'Symbol Name'` can be any string and it is optional. It has no impact on the symbol being created other than helping the developers in debugging. We will see that in the latter part of this article.

There is a specific difference in the creation pattern of a `Symbol` and other primitive types. All other primitive types have literals. For example, the `Boolean` type has two literal values: `true` and `false`. So, we can do like,

```js
let shouldJump = false;
let shouldEat = true;
```
A string literal is zero or more characters enclosed in double (") or single (') quotation marks. We can do like,

```js
let name = 'tapas';
let address = 'somewhere';
```

But, you can not do the same with `Symbol`.  You need to create symbols by calling the function `Symbol()`. Please note, it is not a constructor. Hence, you can not use the `new` keyword to create a symbol.

```js
// This will not work!
const sym = new Symbol('Symbol Name');
```
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1599555445240/bzsu3DrVZ.png)

# But, what is so special about Symbols?
`Symbol` allows us to create unique identifiers. Every time we invoke `Symbol()`, a new unique symbol is created. Two symbols are not equal(they are unique) even when they have the same name,

```js
let symA = Symbol();
let symB =Symbol();
(symA === symB) // false

let symAWithName = Symbol('Name');
let symBWithName = Symbol('Name');
(symAWithName === symBWithName ) // false
```
Also,

```js
typeof Symbol() // is "symbol"
```

# Where can I use Symbols?
As symbols are completely unique, there is some interesting usage of them.

### ⭐ Symbols as unique identifiers
Consider this example where we are trying to get information about a planet by passing the planet as an identifier.

First, we create the constants with the planet identifiers. We are using the `string` based identifier to find the planet information.

```js
const PLANET_MERCURY = 'Mercury';
const PLANET_MARS = 'Mars';
const PLANET_VENUS = 'Venus';
const PLANET_EARTH  = 'Earth';
const PLANET_NEPTUNE   = 'Neptune';
const PLANET_URANUS = 'Uranus';
const PLANET_SATURN = 'Saturn';
const PLANET_JUPITER = 'Jupiter';
```
Next, a function to get the information about the planet,

```js
function getPlanetInformation(planet) {
      switch (planet) {
          case PLANET_MERCURY:
              return `Mercury is 38% the size of Earth. 
                            It is 2,440 km / 1,516 miles`;
          case PLANET_MARS:
              return `Mars is 53% the size of Earth. 
                            It is 3,390 km / 2,460 miles`;
          case PLANET_VENUS:
              return `Venus is 95% the size of Earth. 
                            It is 6,052 km / 3,761 miles`;
          case PLANET_EARTH:
              return `We live here, this is Earth. 
                            It is 6,371 km / 3,959 miles`;
          case PLANET_NEPTUNE:
              return `Neptune is 388% the size of Earth. 
                            It is 24,622 km / 15,299 miles`;
          case PLANET_URANUS:
              return `Uranus is 400% the size of Earth. 
                            It is 25,362 km / 15,759 miles`;
          case PLANET_SATURN:
              return `Saturn is 945% the size of Earth. 
                            It is 58,232 km / 36,184 miles`;
          case PLANET_JUPITER:
              return `Jupiter is 1,120% the size of Earth. 
                            It is 69,911 km / 43,441 miles`;
          default:
              return `Error: Unknown planet. Mostly Alien lives there!!`;
      }
  }
```
As we have the function ready, there are multiple ways to get the planet information. We can do,

```js
console.log(getPlanetInformation(PLANET_EARTH));

// or,
console.log(getPlanetInformation('Earth'));

// or,
let input = 'Earth';
console.log(getPlanetInformation(input));
```
All the above will output, `We live here, this is Earth. It is 6,371 km / 3,959 miles`. 

This is not ideal. You would expect it to throw an error or not to provide the information when anything other than the expected identifiers is passed(example, PLANET_EARTH) while invoking the function. 

As we are dealing with the `string` type here, they are not unique. This may lead to bugs and confusion. So how do we solve it? Use `Symbol` instead.

The only change required in the code above is, declare the identifiers as `Symbol` than `string`.

```js
const PLANET_MERCURY = Symbol('Mercury');
const PLANET_MARS = Symbol('Mars');
const PLANET_VENUS = Symbol('Venus');
const PLANET_EARTH  = Symbol('Earth');
const PLANET_NEPTUNE   = Symbol('Neptune');
const PLANET_URANUS = Symbol('Uranus');
const PLANET_SATURN = Symbol('Saturn');
const PLANET_JUPITER = Symbol('Jupiter');
```

That's all. Rest of the code can stay as is. Now if we do,

```js
console.log(getPlanetInformation(PLANET_EARTH));
```
The output will be,

```shell
We live here, this is Earth. It is 6,371 km / 3,959 miles
```
But the following invocation will result into an error,

```js
 console.log(getPlanetInformation(Symbol('Earth')));
```
Output,

```shell
Error: Unknown planet. Mostly Alien lives there!!
```

### ⭐ Symbols as Object property keys
Symbols can be assigned as a key to an object. This will make sure, the object keys are unique and there are no chances of the object key clashing. Usually, object keys are string types. In contrast to string, symbols are unique and prevent name clashes.

```js
const MY_KEY = Symbol();
const obj = {};

obj[MY_KEY] = 'some_key';
console.log(obj[MY_KEY]); // some_key
```
You can specify the key of a property via an expression, by putting it in square brackets.

```js
let MY_KEY_SYM = Symbol();
  let obj = {
    [MY_KEY_SYM] : 'Tapas'
}
console.log(obj[MY_KEY_SYM]); // Tapas
```
We can also do it with method definition,

```js
let obj2 = {
    [MY_KEY_SYM](){
      return 'GreenRoots'
    }
}
console.log(obj2[MY_KEY_SYM]()); // GreenRoots
``` 

As symbols can be used as a key of an object, we need to be aware of how to enumerate them. 

Here is an object with two properties. One with `Symbol` as key and another one is regular string-based key.

```js
let obj = {
    [Symbol('name')]: 'Tapas',
    'address': 'India'
};
```
What do you think, the output of the following lines?

```js
console.log(Object.getOwnPropertyNames(obj));
console.log(Object.getOwnPropertySymbols(obj));
console.log(Reflect.ownKeys(obj));
console.log(Object.keys(obj));
```
The output,

```shell
["address"]
[Symbol]
["address", Symbol]
["address"]
```
There are only a couple of ways we can enumerate on symbols,
- Using the `getOwnPropertySymbols(obj)` method
- Using the `Reflect.ownKeys(obj)` API.


### ⭐ Symbols as Object meta data
We can use symbols as object keys and it is not enumerable using regular ways of, `Objet.keys(obj)`, `Object.getOwnPropertyNames(obj)`. So it means, we can store some secondary information(like, metadata) that is not required to fetch out when we enumerate the object.

```js
let obj = {
    [Symbol('created-at')]: '1599568901',
    'address': 'India',
    'name': 'Tapas'
};
```
Here the property `created-at` is the metadata information of the object. Hope it makes sense.

# Symbols have debuggability
Try this,

```js
let aSymbol = Symbol('A Symbol');
console.log(aSymbol);
```
Output,

```shell
Symbol {}
```
If you just have one symbol, in the entire application, not a problem. I am sure, that will be a rare case. When you have multiple symbols, getting an output like the above could be confusing. 

The parameter(symbol name) we pass while creating a `Symbol` may be useful for debugging and identifying a symbol correctly.

```js
console.log(Symbol('A Symbol').toString() === 'Symbol(A Symbol)')
```
The above code returns `true`.

# Converting Symbols to other primitive types
You can’t coerce symbols to strings. `Coerce` means implicitly converting from one type to another.

```js
const sym = Symbol('My Symbol');

const str1 = '' + sym; // TypeError
const str2 = `${sym}`; // TypeError
```
However, you will be able to do an explicit conversion.

```js
const sym = Symbol('My Symbol');

const str1 = String(sym); // 'Symbol(My Symbol)'
const str2 = sym.toString(); // 'Symbol(My Symbol)'
```
This is probably the most useful conversion one should be aware of. But there are other types of implicit and explicit conversions you may want to know. Here is a table that shows the conversion list,

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1599570160160/0z2EB7TdB.png)
Credit: Screenshot from [exploringJS book](https://exploringjs.com/es6/ch_symbols.html)

# Reusable Symbols
`Symbols` are completely unique, except in a special situation. Symbols can be created in a `global symbol registry` and fetched from it. This feature enables you to create and share a symbol within an application and beyond.

This registry is `cross-realm`. It means a symbol created in the global registry from the current application frame will be accessible from an iframe or service worker.

Use `Symbol.for()` to create a symbol in the global registry. Note, if a symbol is created multiple times using the same name in the global registry, it returns the already created one.

```js
console.log(Symbol('aSymbol') === Symbol('aSymbol')); // false, as they are local symbols.
console.log(Symbol.for('aSymbol') === Symbol.for('aSymbol')); // true, as created in the global registry.
```
How do we know, if a symbol has been created locally or globally? We have another useful method called, `Symbol.keyFor`. Check this out,

```js
let globalASymbol = Symbol.for('aSymbol');
let localASymbol = Symbol('aSymbol');

console.log(Symbol.keyFor(globalASymbol)); // aSymbol
console.log(Symbol.keyFor(localASymbol)); // undefined
```

# Is it worth knowing about Symbols?
Yes, it is. Symbols are a great tool to create uniqueness for keys, properties, variables. If you look back at your application, you will surely find places to incorporate symbols.

Apart from whatever we have learned so far, there are some "well-known" symbols. These are a bunch of static properties of the `Symbol` class. These are implemented within other JavaScript objects, such as Arrays, Strings, and also within the internals of the JavaScript engine.

The good news is, you can override them and make it as per your own implementations. Please note, the detailed explanations of these `well-known` symbols are outside of the scope of this article. But, we need to know them at a high level, at least. A future article will cover them in depth.

Here is the list of `well-known` symbols:
- Symbol.hasInstance
- Symbol.iterator
- Symbol.unscopables
- Symbol.match
- Symbol.toPrimitive
- Symbol.toStringTag
- Symbol.species
- Symbol.split
- Symbol.search
- Symbol.replace.
- Symbol.isConcatSpreadable

Please check them in detail from the [MDN site](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol).

# Summary
`Symbol` sounds complex but it is not. I wanted to explain the concept and usage of symbols in a simple way as possible. Please let me know if I was successful. A future article will explain the `well-known` symbols in detail. 

To summarize,
- Symbols are added as a feature to ES6.
- Symbols are mostly unique, except when created in the global registry.
- The uniqueness of symbols makes them useful as object properties, feature detection(the planet example), and defining the metadata of an object.
- Symbols can be created using the function, `Symbol()` which optionally takes a name as an argument.
- Symbols are not coercible into primitives(except boolean). It is object-coercible, it coerces it to an object.
- With `Well-Known` symbols we can override the native implementation of JavaScript. It helps in achieving [metaprogramming](https://en.wikipedia.org/wiki/Metaprogramming) with JavaScript.

All the code used in this article can be found @,

- {% github atapas/knowing-es6-symbols %}
- [ES6 Symbol - DemoLab](https://demo.greenroots.info/js/js-symbols/)

<hr />
If it was useful to you, please Like/Share so that, it reaches others as well.

You may also like,
- [JavaScript: Why Reflect APIs?](https://blog.greenroots.info/javascript-why-reflect-apis-cjx09kad20006sos1pmumyn38)
- [Metaprogramming: An Introduction to JavaScript(ES6) Proxy](https://blog.greenroots.info/metaprogramming-an-introduction-to-javascriptes6-proxy-cjwkk64ly000gvds10db4l034)

Follow me on twitter [@tapasadhikary](https://twitter.com/tapasadhikary)