keep in mind...
- for/of loops are not for objects!
- use break to stop the loop
- now possible to create custom iterators
Compared to the old for (var i = 0; i < array.length; i++){}, the newer forms of Javascript loops make code a lot simpler.
For/of loops are the new default for most folks, and for good reason. They can be used to iterate over things like arrays, sets, maps, strings, and iterables, but notably not over objects. Array example:
const cars = ['chevy', 'prius', 'jeep']; for (const car of cars) { console.log(car); };
Maps use key, value pairs, and you can get at the keys and values using an array inside of your for/of loop:
const kids = new Map([['jack', 'boy'], ['suzy', 'girl']]); for (const [key, value] of kids) { console.log(`key: ${key}, value: ${value}`); }
Sets are a little like an array, but the elements must be unique - so duplicates are discarded.
const kids = new Set(['jack', 'suzy', 'betty', 'jack']); for (const kid of kids) { console.log(kid); }
Strings can be iterated too!
const name = 'Antonio'; for (const letter of name) { console.log(letter); }
For/in loops are just for objects - so don't confuse the "in" with f"! You can iterate over the values for keys like so:
const kid = {name: 'Billy', 'grade': 4, 'sport': 'tennis'}; for (const key in kid) { console.log(kid[key]); }
for/each loops only work with arrays, and you cannot stop execution by continue or break statements. They do make code easy to read.
const cars = ['chevy', 'prius', 'jeep']; cars.forEach(function(car){ console.log(car); });
So what if you want to stop execution of your loop based on some condition or value? With for/of and for/in loops (however not forEach ones), you can use:
break
breaks or stops the loop (read more @ mdn).
const cars = ['chevy', 'prius', 'jeep']; for (const car of cars) { if (car == 'prius') break; console.log(car); };
continue
breaks one iteration (in the loop), going to next iteration. (read more @ mdn).
const cars = ['chevy', 'prius', 'jeep']; for (const car of cars) { if (car == 'prius') continue; console.log(car); };
return
stops loop completely, also exits any parent function and executes no more code
const getCars = () => { const cars = ['chevy', 'prius', 'jeep']; for (const car of cars) { if (car == 'prius') return; console.log(car); }; console.log('end of the getCars function'); }; getCars();
You may not realize it, but under the hood, for...of loops are actually using Symbol.iterator already. One thing that's cool is the 'done' attribute, saving you the trouble of figuring out manually when your loop is done, & you need something else to happen. It also keeps track of the iteration number for you, which can be helpful if you've been doing it manually.
Here is a simple array, and resulting iterator, looking "under the hood"...
var arr = [1, 2, 3]; var iterator = arr[Symbol.iterator](); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
This new ES6 concept allows you to roll your own iterators as well, and create a custom and structured iterator in which you choose which values to return for each iteration. If you're wondering why the heck you'd want to go to that trouble... here a few scenarios where it makes sense.
to chunk large data
This wouldn't make a lot of sense with just 10 posts, but if you had a lot of posts, it is an easy way to paginate or load in chunks, where the iterator keeps track of what number you're on.
let posts = ['post 1', 'post 2', 'post 3', 'post 4', 'post 5', 'post 6', 'post 7', 'post 8', 'post 9', 'post 10']; let it = posts[Symbol.iterator](); function loadPosts(iterable, count) { for (let i = 0; i < count; i++) { console.log( iterable.next().value ); } } loadPosts(it, 5); loadPosts(it, 5);
Perform cool regex actions
As somebody who does a lot of regexy stuff in the various e-learning apps I've done, I can see why doing this in your iterator would be great, and keep things really clean.
let Sentence = function(str) { this._str = str; } Sentence.prototype[Symbol.iterator] = function() { var re = /\w+/g; var str = this._str; return { next: function() { var match = re.exec(str); if (match) { return {value: match[0].toLowerCase(), done: false}; } return {value: undefined, done: true}; } } }; var words = new Sentence('Hey have an AWESOME day!'); for (let word of words) { console.log(word); }
with a generator function
Generator functions (read more here) look pretty foreign to anybody used to writing pre es6 Javascript.
function* generateMe() { yield 'i'; yield 'love'; yield 'programming'; }; const gen = generateMe(); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); console.log(gen.next());
The magic of generators is that they can be paused as they are running, and return multiple values as execution pauses and resumes. Sounds cool but what would that actually look like? One example is an infinite loop - which would normally crash your browser! Since you pause after each next() statement, that isn't a problem with a generator.
function *infiniteLoop() { let start = 0; let val = 1; while(val > start) { yield val++; } } let loop = infiniteLoop(); console.log(loop.next()); console.log(loop.next()); console.log(loop.next()); console.log(loop.next());
You can combine the spread operator (...) with a generator to produce a quick array.
function* generateMe() { yield 'i'; yield 'love'; yield 'programming'; }; const gen = generateMe(); console.log([...gen]);
A generator can be used here to simplify the Sentences iterator you created above.
let Sentence = function(str) { this._str = str; } Sentence.prototype[Symbol.iterator] = function*() { var re = /\w+/g; var str = this._str; var match; while (match = re.exec(str)) { yield match[0].toLowerCase(); } }; var words = new Sentence('Hey have an SUPER day!'); for (let word of words) { console.log(word); }
Review of [Symbol.iterator] & Generators
A great summary is found here at code.tutsplus.com
Iterators and generators come in handy when you want to process a collection incrementally. You gain efficiency by keeping track of the state of the collection instead of all of the items in the collection. Items in the collection are evaluated one at a time, and the evaluation of the rest of the collection is delayed till later.
Iterators provide an efficient way to traverse and manipulate large lists. Generators provide an efficient way to build lists. You should try out these techniques when you would otherwise use a complex algorithm or implement parallel programming to optimize your code.