The Symbology of JavaScript Symbols
Tags: javascript, symbols
Published: 2019-12-30 19:52:03

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/0la4np2r6w9bnejlqgav.gif)

There have been two things that have appeared in newer versions of JavaScript that I haven't understood: Symbols and Iterators. 

When it comes to code I have to learn by doing, no amount of reading Mozilla Developer Network was going to help me understand Symbols. 

So I finally decided to stop reading articles, crack open a JavaScript console, snap in to a Slim Jim, and figure out what Symbols are all about. 

And now that I think I understand some basics, I want to share them with you. 

## JavaScript Symbols are a new primitive
If you read the [entry on the Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) it'll tell you right there at the beginning:

> Symbol is a primitive data type

That's a big deal. The Symbol is right up there with `String`, `Boolean`, `Number`, `undefined`, and nobody's favorite, `null`. 

Primitives are a bit like the protons and electrons; you mix and match them to make atoms (objects). Put enough of them together and you can make a ~~dying~~ Death Star (it's called NPM).

So the fact that I've been ignoring `Symbol` is bad; I've been ignoring a fundamental piece of how we build in the JavaScript universe. 

## Symbols are unequivocally unique
This is something that's easy to read on (digital) paper, but maybe hard to accept in practice: When you create a `Symbol` it is unique. Forever and for-always. 

```
let ianMalcom = Symbol('Ian Malcom');
let ianMalcomClone = Symbol('Ian Malcom');

const ianMalcomWasWrong = (ianMalcom == ianMalcomClone); // false
const michaelCrichtonWasWrong = (ianMalcom === ianMalcomClone); // false
```

Not only is every symbol ever created unique, you can't even compare two symbols with the same "value". 

## Symbols don't coerce
Remember everyone's favorite gag, that old `1 + '1' == '11'`? Or `[1] + 1 == 11`? I know you remember the one about the boolean throwing the baseball at the window: `true * ([1] + [1]) == 11`

Those are all fun type coercion games that we hope never come up in job interviews. 

Well guess what? 

Symbol don't play like that. 

```
const zildjian = Symbol('1');
zildjian + 1; // TypeError: can't convert symbol to number
zildjian + '1'; // TypeError: can't convert symbol to string
!!zildjian; // true. Huh.
```

So Symbols don't give into our concatenation shenanigans.

*So what are Symbols actually good for?*

What good is an absolutely unique primitive that doesn't give into JavaScript's Weird Coercion Tricks®?

## Symbols are Secret(ish)

Let's suppose some sort of library that we're bringing into our codebase. We're making some dinosaurs, but maybe we don't have that bit about getting dinos to reproduce. 

The old school way might be something like this:

```
import dinosaurs from 'dinosaurs';

const tRex = Object.assign(
  {
    reproduce() {
     return null;
    }
  },
  dinosaurs); 
```

This seems fine, except ... it's easy to create a scenario where everyone dies:

```
// Step 1. Let's create a bigger t-rex
const megaTRex = Object.assign({}, tRex);

// Step 2. t-rexes get lonely because they have no one to hug
megaTRex.reproduce = function () {
  return this;
};

// Step 3. You know what no one asked for? Velociraptor + t-rex
const velociTrex = Object.assign(velociraptor, megaTrex);


// Step 4. Hey, turns out we've got this .reproduce function, WCGW?
velociTrex.reproduce(); // returns zero rescues from Chris Pratt

```

Sometimes, when we add a feature on to an object, we want it _just_ for that object. We don't want others to see what we've been doing and to use it themselves.  



## Symbol is a secret password

What if we created our own private way to help that `megaTRex` reproduce? One that no one else could know about? 

Let's go back to our `megaTRex` and try again. But this time, we'll use a `Symbol` as the _key_ for our object (this is called a symbol-keyed property):

```

const megaReproducer= Symbol('reproduce');

const megaTRex= Object.assign({}, tRex);

megaTRex[megaReproducer] = function () { 
  return this;
}

```

Guess what? You're not going to find that on the object! If you trying to find this `megaReproducer` with `for-in`, you're not going to find it. 


In the Firefox console, you'll see something like this if you inspect the object:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/dvq3wny0cai1wlh7bzla.jpg)

You can see that it's there, but you're not going to find it in any of your typical approaches you might think of for finding properties on an object or its prototype. 

None of these will reveal a property-keyed Symbol:


```
for (property in megaTRex) {
 console.log(megaTrex[property])
}

Object.keys(megaTRex).forEach(property => console.log(propertyName));

for (let [propertyName, value] of Object.entries(megaTRex)) {
  console.log(propertyName, value);
}
```


You'll have to use `Object.getOwnPropertySymbols` if you want to find the symbols living on an object. So it's not like Symbol-keyed property is invisible; it's just chilling on an island you weren't thinking of looking on. 

But, looking is one thing. How do you access it?

### It takes a symbol to know a symbol

You might be able to inspect an object and see that a symbol is a key on it. But you don't have any way to access it &mdash; _unless you have the symbol you used to create it_. 

What this means is that we get a good fair amount of privacy and protection for our objects. The `Symbol` is a way to extend an object, maybe even an object you don't really "own" -- and do it in a safe way where you don't open up risks of abuse elsewhere in your runtime. 

The only way this `megaTRex` is going to make a baby T-rex is if you have that exact `Symbol`:

```
const newMegaTRex = megaTrex[megaReproducer]()
```

This is especially practical in cases where we are importing from a library and exporting code to be used elsewhere:

```
import {tRex} from 'dinosaurs.js';
const keyToReproduction = Symbol('frogs');

tRex[keyToReproduction] = function () {

    return this;

}

export const megaTRex = tRex[keyToReproduction]();
```

We've safely extended our library and exported a product _after_ that extension &mdash; without exporting the ability to access that function. 

## Symbols don't collide
Going back to the fact that a `Symbol` is absolutely unique. This turns out to be handy for another reason: it prevents accidental overwrites. 

Let's step away from uncited Michael Crichton fan-fiction for a minute and talk through a slightly more practical example: extending the `console`. 

Let's suppose we're dealing with a particularly fickle library, and we need to do a lot of logging. 

Maybe we want to have a nice formatted `console.log`, because we're developers and of course we'd want this. 

```
let pretty= Symbol('logPretty');

console.log[pretty] = function (message, styles= 'font-size: 1.5em; color: #bad') {
  console.log(`%c ${message}`, styles);
}
```

Cool. Because we've used a `Symbol` for extending the console, we're safe from any browser ever adding `console.log.pretty` in the near or distant future. 

### This is a great way to extend globals in the browser!


As long as we've got access to that `pretty` variable, we can write `console.log[pretty]('Hey there')` and see all the delightful console messages we want. 

Just, uh, remember that you need that _exact_ symbol. Which means...


### Avoid Collisions with `const`

You may have noticed that I used `let` in my example above. 
This is bad. Don't do that. 

```
// uh oh. I reassigned my variable
pretty = Symbol('newPretty');
console.log[pretty] = function (message, styles = 'font-size: 3em; color: red') {
  console.log(`%c ${message}`, styles)
}
```

Now I have no easy way to get back my old "pretty" symbol. 

I should've used `const` so my variable couldn't be reassigned. That was dumb. 

### Retrieving lost Symbols

How can I ever outshine other developers on my team without this precious symbol? Will I ever be able to get my long lost symbol back, so that I can make my logs pretty again?

Of course. I need to use `getOwnPropertySymbols` and quit being melodramatic:

```
const [oldPretty, newPretty] = Object.getOwnPropertySymbols(console.log);

```

Which reminds me...

### Describe your Symbols

Wen you create a Symbol, it doesn't _need_ a 'descriptor'. You can make plain ol' ordinary undescriptive `Symbol`s:

```
const prettyBad = Symbol();

```

Much like cloning dinosaurs, this is probably a bad idea. 

If you're using Symbol-keyed properties, and you need  to use `getOwnPropertySymbols`, that descriptor is going to be the key to figuring out which is the prettiest log of them all:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/sk2zhhpeemtxrnaeul9n.jpg)

I should add, by the way, that while you _could_ use the same descriptor for every symbol-keyed property, that doesn't mean you _should_:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/mwluj12jjvcc3ole0rx7.jpg)


## Symbols don't `stringify()`
`JSON.stringify` ignores Symbols completely. 

```
import { dna } from 'dinosaurs';

const reproduction = Symbol('frogs');
const howToCloneDinosaurs = {
  richDudes: 1,
  newman: 0,
  cynicalMathematicians: 1,
  paleontologists: 2,
  island: 'isla nublar',
  lawyers: Infinity
};

howToCloneDinosaurs[reproduction] = dna;

```
![console output of json.stringify where lawyers are deservedly null and a symbol-keyed property isn't there](https://thepracticaldev.s3.amazonaws.com/i/tee11d4lba3mwi8j2as5.jpg)

I think this is a Good Thing™. 


### It prevents cloning
The most common way to deep-clone objects in JavaScript is with `JSON.parse(JSON.stringify())`. 

A Symbol-keyed property is a simple and terse way to put a property on an object that you don't want to be cloned. 

 Of course, you can _also_ use the always-clunky, super verbose, always-have-to-look-it-up `Object.defineProperty()` to make a property unJSON.stringifiable:
  
```
Object.defineProperty(howToCloneDinosaurs,'reproduction', {
 value: dna,
 enumerable: false
});
```

`Object.defineProperty` might make sense when we need to define a `lot` of things about a property. But if we want an easy way to make sure the property and its value aren't cloned, Symbol seems the way to go. 

## There's some built-in Symbols
[Turns out, there's a slew of "built-in" symbols that exist](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#Well-known_symbols). I won't list them all here, but there's a few that catch my eye as particularly interesting:


* `Symbol.iterator`
* `Symbol.asyncIterator`
* `Symbol.split`
* `Symbol.toStringTag`

The reason these are of interest to me (and should be of interest to you) is because these are "Symbol Keys" that allow us to define our own behaviors on objects. These behaviors didn't used to be available to us, but now they are!

### Create a String that iterates by word
`for of` is kinda awesome, but it only works on things that are _iterable_ (more on what that means in another post. 

Let's use `Symbol.iterator` and make a string iterable:

```
function WordString(text) {
    const string = new String(text); // make explicit object
    const words = string.split(' '); // split by spaces
    let wordIndex = 0;

    string[Symbol.iterator] = function* stringIterator() {
      while (wordIndex < words.length) {
       yield words[wordIndex++]
        .replace(new RegExp('[!.?]', 'g'),''); // remove any punctuation
      }
    }
  
    return string;
}

```

Ignore the `*` and the `yield` for right now. Those are things for iterators. Just dial in on the fact that we used a global Symbol key (`Symbol.iterator`) and we used it to make something that _wasn't iterable_ ... *iterable*. 

Look at what we can do with this fancy `WordString` now:

![Output in a console using for-of where each word in the string "ok. I think I get it now..." is logged](https://thepracticaldev.s3.amazonaws.com/i/fyvaejs5domvfzh2mup2.jpg)


### Create an Honest Array 
If you read my [previous post on arrays](https://dev.to/paceaux/kyle-simpson-proved-i-still-don-t-know-javascript-2fnp) you might recall that there's an implicit and explicit `undefined`. Maybe you're disappointed that arrays are liars sometimes. 

Let's use `Symbol.species` to tell us that this is still an array. And then we'll throw a generator function on that array and define what `for of` will actually return:

```

class TruthyArray extends Array {
    constructor(value) {
        super(...value);  
        this.value = [...value];
    }
    get [Symbol.species]() {
      return Array;
    }
    *[Symbol.iterator]() {
      let itemIndex = -1;
          while (itemIndex < this.value.length ) {
              if (this.value[++itemIndex]) {
                  yield this.value[itemIndex];
              }
          }
      }
  }

```

Again, ignore the `*` and the `yield`. That's for another time. 

The greater point is that `Symbol` has some built-in "keys" that we can add to an object to extend functionality. 

## The Recap
Dev.to says this is a 9-minute read. That's like 2 cigarette breaks or one visit to the bathroom after a tryst with a holiday cheese plate. 

I don't want to keep you much longer or someone will be looking for you... and, "I was reading an article about Frank's Jurassic Park-inspired JavaScript Symbol fan fiction,"  is not how you want to explain yourself. You're a professional. 

1. `Symbol` is a primitive. It's worth knowing because it's in the guts of how everything works in JS now. 
2. Symbols are unique and it's best not to think about _how_ they're unique. But they are.
3. Symbols don't coerce into other things. They laugh in the face of your `+` jokes. 
4. You can and probably _should_ be making property-keyed symbols. 
5. Symbol-keyed properties guarantee you'll never have collision; that makes it the perfect way to extend browser built-ins or libraries. 
6. Symbol-keyed properties are hidden-ish. `JSON.stringify` ignores them, `for-in`, `Object.keys` ignores them, too. You have to know you're looking for a property-keyed  Symbol. 
7. You need your Symbol to access a symbol-keyed property, so use `const` for defining it, or otherwise make sure you throw descriptors on that sucker lest it become lost forever and foralways. 
8. It's not just about `Symbol`. The Symbol is how we access utilities previously unavailable to us on objects. 


### Shoutouts
Thanks to [Isabela Moreira](https://isabela.dev/) and [Alex Klock](https://github.com/alex-klock) for providing technical review of this.