Javascript has a number of iteration tools for-loop, iterator pattern and Generators which i will cover in this section.
The for-loop is the traditional tool for iteration, however Javascript does have some others like the iterator pattern which I am going to cover.
Basic for-loop example | for (let i = 1; i <= 10; ++i) { console.log(i); } let collection = ['foo', 'bar', 'baz']; for (let index = 0; index < collection.length; ++index) { console.log(collection[index]); } |
Collection forEach() example | let collection = ['foo', 'bar', 'baz']; collection.forEach((item) => console.log(item)); |
Iterator pattern is one of the design patterns from the gang of four, it describes something that is iterable and can inplement a formal Iterable interface and be consumed by a Iterator. The Object created would return a Iterator that would be able to iterate over the elements inside the Object. Built-in objcts likes Strings, Arrays, Maps/Sets already have a iterator.
Check existence of iterator | // These types do not have iterators let num = 1; let obj = {}; console.log(typeof num[Symbol.iterator] === 'function'); // false console.log(typeof obj[Symbol.iterator] === 'function'); // false // These types all have iterators let str = 'abc'; let arr = ['a', 'b', 'c']; let map = new Map().set('a', 1).set('b', 2).set('c', 3); let set = new Set().add('a').add('b').add('c'); console.log(typeof str[Symbol.iterator] === 'function'); // true console.log(typeof arr[Symbol.iterator] === 'function'); // true console.log(typeof map[Symbol.iterator] === 'function'); // true console.log(typeof set[Symbol.iterator] === 'function'); // true |
Iterators in action | // for...of loops let arr = ['foo', 'bar', 'baz']; for (let ele of arr) { // foo, bar, baz console.log(ele); } // Array destructuring let [a, b, c] = arr; console.log(a, b, c); // foo, bar, baz // Spread operator let arr2 = [...arr]; console.log(arr2); // ['foo', 'bar', 'baz'] // Array.from() let arr3 = Array.from(arr); console.log(arr3); // ['foo', 'bar', 'baz'] // Set constructor let set = new Set(arr); console.log(set); // Set(3) {'foo', 'bar', 'baz'} // Map constructor let pairs = arr.map((x, i) => [x, i]); console.log(pairs); // [['foo', 0], ['bar', 1], ['baz', 2]] let map = new Map(pairs); console.log(map); // Map(3) { 'foo'=>0, 'bar'=>1, 'baz'=>2 } |
Obtain and Use a Iterator | let names = ["Paul", "Will", "Moore", "Graham"] let iter = names[Symbol.iterator](); // obtain the iterator console.log(iter.next()); // { value: 'Paul', done: false } console.log(iter.next()); // { value: 'Will', done: false } console.log(iter.next()); // { value: 'Moore', done: false } console.log(iter.next()); // { value: 'Graham', done: false } console.log(iter.next()); // { value: undefined, done: true } - done is now true, no more elements |
Custom Iterator | class Counter { // Counter instance should iterate <limit> times constructor(limit) { this.count = 1; this.limit = limit; } next() { if (this.count <= this.limit) { return { done: false, value: this.count++ }; } else { return { done: true, value: undefined }; } } reset() { this.count = 1; // reset the count back to one } [Symbol.iterator]() { return this; } } let counter = new Counter(3); for (let i of counter) { console.log(i); } counter.reset(); // need to reset the the count back to 1 console.log(counter.next()); console.log(counter.next()); console.log(counter.next()); console.log(counter.next()); Output -------------------------------------------------- 1 2 3 { done: false, value: 1 } { done: false, value: 2 } { done: false, value: 3 } { done: true, value: undefined } |
Generators allow you to pause and resume code execution inside a function block, below is a how they work
Generators that the form of a function, and use a asterisk to indicate its a generator, they implement an Iterator interface which means they have a next() method, which begins or resumes the execution, the yield keyword allows the generator to stop and start execution, when the generator encounters the yield keyword the execution will halt and the scope of the function will be preserved, it will only start again then next() is called.
Basic Generator | function* generatorFn() { // notice the asterisk console.log("foo"); yield 'foo'; console.log("bar"); yield 'bar'; console.log("baz"); return 'baz'; } let generatorObject1 = generatorFn(); let generatorObject2 = generatorFn(); console.log(generatorObject1.next()); // { done: false, value: 'foo' } console.log(generatorObject2.next()); // { done: false, value: 'foo' } // At this point we are positioned at console.log("bar") ready to continue console.log(generatorObject1.next()); // { done: false, value: 'bar' } console.log(generatorObject2.next()); // { done: false, value: 'bar' } // At this point we are positioned at console.log("baz") ready to continue console.log(generatorObject1.next()); // { done: false, value: 'baz' } console.log(generatorObject2.next()); // { done: false, value: 'baz' } Output ----------------------------------------------------------------- foo { value: 'foo', done: false } foo { value: 'foo', done: false } bar { value: 'bar', done: false } bar { value: 'bar', done: false } baz { value: 'baz', done: true } baz { value: 'baz', done: true } |
Generator example | // Infinite counting generator function, util program restarts function* generatorFn() { for (let i = 0;;++i) { yield i; } } let generatorObject = generatorFn(); console.log(generatorObject.next().value); // 0 console.log(generatorObject.next().value); // 1 console.log(generatorObject.next().value); // 2 console.log(generatorObject.next().value); // 3 console.log(generatorObject.next().value); // 4 console.log(generatorObject.next().value); // 5 |
Yielding an Iterable | // generatorFn is equivalent to: // function* generatorFn() { // for (const x of [1, 2, 3]) { // yield x; // } // } function* generatorFn() { yield* [1, 2, 3]; // Notice the asterisk } let generatorObject = generatorFn(); for (const x of generatorFn()) { // results in 1, 2, 3 console.log(x); } |
Returning early | function* generatorFn() { for (const x of [1, 2, 3]) { yield x; } } const g = generatorFn(); console.log(g); // generatorFn {<suspended>} console.log(g.return(4)); // { done: true, value: 4 } console.log(g); // generatorFn {<closed>} Note: you can also use throw() |