Iterator Pattern

Javascript itself has iteration built into the language

  • there are a number of for and forEach methods to iterate through arrays
  • we have the forIn loop to iterate through objects

Iterator pattern is different as it doesn’t run in a synchronous loop and the iterator keeps track of which items have been processed

  • we manually choose when to extract the data of the next item
  • the purpose is to provide access to the items in a collection contiguously, while keeping track of which items have been accessed
  • common to have a next() method which returns next item in collection
  • some libraries have hasNext, isDone, first, and reset()
  • in JS ES7, iterators will be added as a core part of the language, we have to create our own for the time being, look here for more info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators

Step 1 – Page: iterator.js


// JavaScript Document
define(function() {
	'use strict';
	
	var Iterator = function(collection) { //define object constructor, which is passed a collection of items
		this.collection = collection; //store this collection as a property of the object
		this.index = 0; //set up an index property to keep track of which items have been processed.  Initialized to 0
	};
	
	Iterator.prototype = {
		constructor: Iterator, //first thing we need to do is set constructor property for the prototype object to the constructor we created.  If we don't do this, the constructor will point to the object constructor instead.
		next: function () {
			return this.collection[this.index++]; //this is the next method, which is a critical component of an iterator.  We are simply returning the next item in the collection with the index property, then we are incrementing it by 1
		},
		isDone: function () {
			return this.index === this.collection.length; //set up isDone method that returns true once all the items in the collection have been iterated through.  We are returning whether the index equals the length of the collection (# of items in the array)
		},
		reset: function () { //set up reset method to set the index back to 0, in case we want to iterate again
			this.index = 0;
			return this; //then return the iterator so that other methods can be chained on after the reset
		},
		take: function (numberOfItems) { //the take method will return a specified number of items and update the index accordingly.  It will receive the number of items we want to take as an argument
			var newIndex = this.index + numberOfItems, //calculates what the new index will be by adding numberOfItems to the current index and stores this in a variable
				newArr = Array.prototype.slice.call(this.collection, this.index, newIndex); //we then create a new array using the slice method.  Pass the collection to the call method to as the first argument to set the collection as the this object.  We then pass in the current index as the index to start slicing from. The newIndex is the set as the second argument
				
			this.index = newIndex; //set iterators index to the new index
			return newArr;	//return the new array
		}
	};
	
	return { //return an object from the module that has one build method.  This is an implementation of the factory pattern
		build: function (collection) { //build method is passed the collection.  We only need this if object is passed to iterator, in which case we have to build an array for us to work with the data.
			var keys = Object.keys(collection), //keys method of object constructor will create an array of all the keys in the object.
				tempArray = [],
				prop;	
				
			if (typeof collection === 'number') { //if a number is passed into the iterator
				collection = collection.toString();	//convert the number to a string and the iterator can work with it
			}
			
			if (keys.length) { //if object is passed, it is a collection of key value pairs that can be parsed
				for (prop in collection) { //extract the values from the object
					tempArray.push(collection[prop]);	//construct a new array from these values
				}
				collection = tempArray;//send these values to the iterator
			}
			
			if (collection.length) { //if the collection exists
				return new Iterator(collection); //initialize the iterator
			} else {
				throw ('Iterator cannot be built from Boolean, null, or undefined data');	
			}
		}
	};
});

Step 2 – Page: init.js


define(function(require) {
	'use strict';

	return {
		init: function() {
			var iterator = require('iterator/iterator'), //load the iterator
				testArray = [{ something: 'yay', other: 123}, {something: 'test', other: 456}], //create a test array
				myArrayIterator = iterator.build(testArray), //build the array into the iterator, where we can then iterate through data
				testString = 'teststring', //set up test string
				myStringIterator = iterator.build(testString); //send testString to iterator
				
			console.log(myArrayIterator.next()); //log the data from the first index in the array
			console.log(myArrayIterator.next()); //log the data from the next index
			
			while (!myStringIterator.isDone()) { //loop through test string
				console.log(myStringIterator.next());	//log the next data in the testString loop
			}
			
			console.log(myStringIterator.reset().take(4).join('')); //we can reset the iterator, then use the take method to choose single values from the array (this will grab the first 4).  We then join the data to create a word.
		}
	};
});

Step 3 – main.js


// JavaScript Document
require (
	['iterator/init'],
	function (iterator) {
			'use strict';
	
			var examples = {
					iterator: iterator
				};
			
			window.runExample = function (example) {
				examples[example].init();
			};
	}
);

Conclusion

Iterator pattern isn’t as common is JavaScript, but it will soon become a core component of the specification. It decouples the processing of a collection of items, from the collection itself

  • we don’t need to know what the data is or what type, the iterator is designed to control for all events.