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