3 min read

Special Symbols Seem Super Sensible

by Vihan Bhargava
  • JavaScript
  • ES6

Excuse the horrible alliteration in the title. This is yet another article on JavaScript ES6’s “Symbols” which due to their unique property, can provide much more powerful behavior, in what I’d say is close to operator overloading with them.

Excuse the horrible alliteration in the title. This is yet another article on JavaScript ES6’s “Symbols” which due to their unique property, can provide much more powerful behavior, in what I’d say is close to operator overloading with them.

Note: If you’re not already familiar with Symbols, I highly recommend reading my ES6’s Unique Symbols article in which I explain them in great depth.

No... not these
symbols

#What are they?

These “special symbols”, are pre-defined symbols, which are properties of the Symbol class. A popular “special symbol” is Symbol.iterator, which allows you define behavior of a class when iterated over. Here’s an example of me using the Symbol.iterator property, (don’t loose me, I’ll explain this later):

class Counter {
    *[Symbol.iterator]() {
        let i = 0;
        while (true)
            yield i++;
    }
}

let counter = new Counter();

for (let i of counter) {
     console.log(i); // 1
                     // 2
                     // 3
                     // ...
}

As you can see, we’ve essentially defined custom behavior for our class with Symbol.iterator, pretty cool eh?

#What did you just do?

If you’re not familiar with ES6 class syntax, essentially I created a function (it was a generator, explaining these are for another article), with the key (the name of the function), the special symbol.

So in my overview of symbols article, I explained how every symbol is unique, meaning if I do:

let A = Symbol();

A is now a unique value. Nothing else will ever equal A except A itself.


Additionally, object keys, can be really anything. For example, on this webpage, try doing:

var o = {};
o[ document.getElementsByTagName("body")[0] ] = "Hello, World!"

in your console. Now go ahead and see what the value of o[ document.getElementsByTagName("body")[0] ] is. Spoiler: It’s "Hello, World!". So you can see we can set the key of an object to an element. We can also set the key to a symbol.


So how does this all relate? Well JavaScript makes some predefined symbols for us. It can check if an object has a function/variable, for a specific symbol, and then perform different behavior. Think of JavaScript doing this before every program:

Symbol.iterator = Symbol();
Symbol.match = Symbol();
// ...

Then when you do something like for (i of counter). JavaScript will notice our counter will have a function named with the value of Symbol.iterator, and it uses that functions custom behavior.

Got it? (If you didn’t leave a comment, I’ll be happy to explain it).


Okay, so know you understand at least two things:

  • Object keys can be anything
  • Predefined JavaScript Symbols

How do these combine? Well quite simply: You can set a object’s key to one of these special symbols. The JavaScript engine will see the object has that special symbol and then it can preform different behavior!

Aha, I get it! (I hope that’s what you’re
thinking)
Aha, I get it! (I hope that’s what you’re thinking)

#Examples

Here are some examples just in case you’re having trouble understanding:

var myobject = {};

// Setting the function to "Symbol.iterator" on myobject.
myobject[Symbol.iterator] = function* () { /* ... */ };

Ignore the function*, that’s a generator function, which I’ll go over in another post. The default symbol I’m using is Symbol.iterator, this lets us define special behavior for

  • When it is spread (will talk about later)
  • When it is iterated with for .. of (will also talk about later)

#What they do

They are a couple default Symbols. Here they are:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.match
  • Symbol.replace
  • Symbol.search
  • Symbol.species
  • Symbol.split
  • Symbol.toPrimitive
  • Symbol.unscopables

I’m not going to go over all of them as this blog post would become too long and I don’t have the time, but if you’d like to read more on an individual symbol check out MDN’s Symbol section.

#Symbol.iterator

This is probably the most popular and, in my opinion, the coolest symbol.


Iteration in JavaScript has never been great. There was never a proper way to iterate over objects, and for..in loops were just plain weird. ES6 is like a fixed version of JavaScript, bringing real classes and other features to make JavaScript the powerful language it needed to be; but ES6 didn’t forget about iteration. For backwards compatibility they couldn’t change the for..in loop… but they added the for..of.

This was the future of iteration, it worked hand in hand with the spread operator and generators, but what about customizability? For that reason Symbol.iterator was introduced.


To understand Symbol.iterator you’re going to need to understand generators and iterators, which I’ll link you here. To get a quick idea of generators, essentially they are functions declared as function*, an example:

function* mygenerator() {
    yield 1;
    yield 2;
    yield 3;
}

let generate = mygenerator();
generate.next(); // 1
generate.next(); // 2
generate.next(); // 3

[...mygenerator()] // [1, 2, 3]

Unfortunately generators do not provide power for that. You’ll need
the a polyfill.
Unfortunately generators do not provide power for that. You’ll need the a polyfill.

So can you see yet how these can be used for iteration? Well if an object has the Symbol.iterator property set to a generator, it use that to iterate. Here’s a raw example:

var iterator = {
    [Symbol.iterator]: function*() {
        yield 1;
        yield 2;
        yield 3;
    }
};

for (let i of iterator) {
    console.log(i); // 1
                    // 2
                    // 3
}

Got it? Here’s a more realistic scenario:

class SortedArray {
    constructor(...args) {
        // Sort the arguments in ascending numerical order
        this.value = args.sort((a, b) => a - b);
    }

    // Sort function again
    sort() { return this.value.sort((a, b) => a - b) }

    // The generator with the key as 'Symbol.iterator'
    *[Symbol.iterator]() {
        let target = this.sort();
        for (var i = 0; i < target.length; i++) {
            yield target[i]; // `yield` is like return, except doesn't mark the end
        }
    }
}

let myarray = new SortedArray(1, 5, 4, 2, 3);

for (let i of myarray) {
    console.log(i); // 1
                    // 2
                    // 3
                    // 4
                    // 5
}

[...myarray] // [1, 2, 3, 4, 5]

I hope you understood that. You can improve it by sorting to a temporary array and then mapping the items to properties named in ascending order to mimic an array. Then your SortedArray should be able to extend Array and you’ll get Array’s methods.

#Conclusion

These special symbols are a very powerful reflection tool. They are very useful but require considerable understanding of ES6. I hope this post detailed it out well and I also hope you learned something! Any questions? Feel free to ask in the comments