For...of method and symbols (including symbol methods)

26 May 2020

Working through Rediscovering JavaScript: Master ES6, ES7 and ES8.

For of iterator

There are a lot of moving parts in a traditional for loop. You have to set the iterator, the upper and lower bounds, and how much to iterate by.

For the most part, we use for loops to iterate forwards rather than backwards. In this case, we can replace the traditional for loop with a for of loop:


const names = ['Sara', 'Jake', 'Pete', 'Mark', 'Jill'];
for(const name of names) { console.log(name);
}

We can use the for...of method on any object that is iterable.

We can also use a method called entries to get the index for each of the items in the array. We can store the index and the item in their own variables by descructuring them, as follows:


const names = ['Sara', 'Jake', 'Pete', 'Mark', 'Jill'];
for(const [i, name] of names.entries()) { console.log(i + '--' + name);
}

Symbol

Ahhh, the first part of this section was really difficult to understand, but I think I get it now.

You can use symbols to hide object properties from being iterated over, but only if the propery name is of type symbol.


const age = Symbol('ageValue');

const sam = {
  first: 'Sam',
  email: 'sam@example.com',
  [age]: 2
};

First, we create a constant variable called age, and store a Symbol that contains the string 'ageValue' in it.

Then we create an object called Sam. Sam has a property called first, which stores a first name as a string value. Sam also has a property called email, which stores an email as an email value.

Then we have an age propery, which is surrounded by square brackets. The square brackets means that the age property name will be set to whatever is stored in a variabel called age. So the age property name becomes the string 'ageValue', which is of type Symbol. The value stored in the symbol named property is the number 2.

There is no explanation for why we might want to do this at the moment, but will see if one comes up later.

While you can't access symbol properties when you iterate over them, they are not private or encapsulated. The symbol property can still be accessed and its value can still be changed. You can also see a list of symbol properties by using Object.getOwnPropertySymbols().

You can also create symbols by using the symbol.for() method. When you create a symbol like this, a new symbol is only created if the symbol key (the argument string you pass in) hasn't already been created. Otherwise, you get the symbol you have already created back. Kind of like an object reference instead of an object copy.


const masterWizard = Symbol.for('Dumbledore'); const topWizard = Symbol.for('Dumbledore');
console.log(typeof(masterWizard)); console.log(masterWizard); console.log(masterWizard === topWizard);
console.log(Symbol.keyFor(topWizard));

In the first line, we check in the global registry to see if the key 'dumbledore' already exists. If it doesn't, we create a symbol with the value 'Dumbledore', and assign it to a constant variable called 'masterWizard'.

We then try and do the same thing for a topWizard variable. However, the key 'Dumbledore' already exists in our global registry, so we assign the reference to that symbol to it. So both the masterWizard and topWizard variables contain the same symbol.

The rest of the console logs verify that this is the case.

Special, well-known symbols

Symbol names are unique, which means you can create symbols properties for an object that contain specific method calls.

[Symbol.for('myapp.myMethod')];

This is a roundabout (not perfect) way for JavaScript to implement interfaces, which are contracts where you expect a certain class to contain specific methods.


class SuperHero { constructor(name, realName) {
this.name = name;
this.realName = realName; }
toString() { return this.name; }
[Symbol.search](value) {
console.info('this: ' + this + ', value: ' + value); return value.search(this.realName);
} }

First, we create a class named Superhero. An instance of this class holds two fields, name and realName. The symbol.search() method accepts an argument (given the name value in this case), and searches the realName variable to see if it contains the value passed in. It also prints an informational message about the current object and it's passed in value argument.

Ahh, so basically, the search symbol method overrides the normal search method.


const superHeroes = [
new SuperHero('Superman', 'Clark Kent'),
new SuperHero('Batman', 'Bruce Wayne'),
new SuperHero('Ironman', 'Tony Stark'),
new SuperHero('Spiderman', 'Peter Parker') ];
const names = 'Peter Parker, Clark Kent, Bruce Wayne'; for(const superHero of superHeroes) {
console.log(`Result of search: ${names.search(superHero)}`); }

The code above creates an array of superHero instances. Each superhero instance has a name, like 'Superman' or 'Spiderman', and a real name, like 'Clark Kent' and 'Peter Parker'.

Then we create a variable which contains a list of real names that belong to superheros.

Then, we loop through each of the superhero instances, and print out the index where the superheros real name (based on the instance variable realname), appears in the list of names. If the real name isn't in the list of given names, a -1 is returned.