Skip to content Skip to sidebar Skip to footer

JavaScript Iterator Class

Do you know a JavaScript library that implements a generic Iterator class for collections (be it Arrays or some abstract Enumerable) with a full set of features, like the Google Co

Solution 1:

Ok, the enumerable pattern is not a real iterator then.

Is this (below) useful for you? It conforms to the sematics you gave at least. As usual there are tradeoffs to be made here and there, and I didn't think very hard when deciding this time :).
And maybe you would like to be able to send in a number or two and iterate over a range in that way. But this could maybe be a start (there's support for iterating over hashes, arrays and strings).

It's a whole demo page which runs itself and does some debug output, but the (possibly) interesting stuff is in the

window.npup = (function() {
    [...]
})();

spot.

Maybe it is just me who doesn't get it at all, but what would you use such a java-like Iterator for in a real situation?

Best /npup

<html>
<head>
<title>untitled</title>
</head>

<body>
    <ul id="output"></ul>


<script type="text/javascript">
window.log = (function (outputAreaId) {
    var myConsole = document.getElementById(outputAreaId);
    function createElem(color) {
        var elem = document.createElement('li');
        elem.style.color = color;
        return elem;
    }
    function appendElem(elem) {
        myConsole.appendChild(elem);
    }
    function debug(msg) {
        var elem = createElem('#888');
        elem.innerHTML = msg;
        appendElem(elem);
    }
    function error(msg) {
        var elem = createElem('#f88');
        elem.innerHTML = msg;
        appendElem(elem);
    }
    return {
        debug: debug
        , error: error
    };
})('output');


window.npup = (function () {
    // Array check as proposed by Mr. Crockford
    function isArray(candidate) {
        return candidate &&
            typeof candidate==='object' &&
            typeof candidate.length === 'number' &&
            typeof candidate.splice === 'function' &&
            !(candidate.propertyIsEnumerable('length'));
    }
    function dontIterate(collection) {
        // put some checks chere for stuff that isn't iterable (yet)
        return (!collection || typeof collection==='number' || typeof collection==='boolean');
    }
    function Iterator(collection) {
        if (typeof collection==='string') {collection = collection.split('');}
        if (dontIterate(collection)) {throw new Error('Oh you nasty man, I won\'t iterate over that ('+collection+')!');}
        var arr = isArray(collection);
        var idx = 0, top=0;
        var keys = [], prop;
        if (arr) {top = collection.length;}
        else {for (prop in collection) {keys.push(prop);}}
        this.next = function () {
            if (!this.hasNext()) {throw new Error('Oh you nasty man. I have no more elements.');}
            var elem = arr ? collection[idx] : {key:keys[idx], value:collection[keys[idx]]};
            ++idx;
            return elem;
        };
        this.hasNext = function () {return arr ? idx<=top : idx<=keys.length;};
    }
    return {Iterator: Iterator};
})();

var element;

log.debug('--- Hash demo');
var o = {foo:1, bar:2, baz:3, bork:4, hepp: {a:1,b:2,c:3}, bluff:666, bluff2:777};
var iterator = new npup.Iterator(o);
for (element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
    log.debug('got elem from hash: '+element.key+' => '+element.value);
    if (typeof element.value==='object') {
        var i2 = new npup.Iterator(element.value);
        for (var e2=i2.next(); i2.hasNext(); e2=i2.next()) {
            log.debug('&nbsp;&nbsp;&nbsp;&nbsp;# from inner hash: '+e2.key+' => '+e2.value);
        }
    }
}
log.debug('--- Array demo');
var a = [1,2,3,42,666,777];
iterator = new npup.Iterator(a);
for (element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
    log.debug('got elem from array: '+ element);
}
log.debug('--- String demo');
var s = 'First the pants, THEN the shoes!';
iterator = new npup.Iterator(s);
for (element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
    log.debug('got elem from string: '+ element);
}
log.debug('--- Emptiness demo');
try {
    log.debug('Try to get next..');
    var boogie = iterator.next();
}
catch(e) {
    log.error('OW: '+e);
}

log.debug('--- Non iterables demo');
try{iterator = new npup.Iterator(true);} catch(e) {log.error('iterate over boolean: '+e);}
try{iterator = new npup.Iterator(6);} catch(e) {log.error('iterate over number: '+e);}
try{iterator = new npup.Iterator(null);} catch(e) {log.error('iterate over null: '+e);}
try{iterator = new npup.Iterator();} catch(e) {log.error('iterate over undefined: '+e);}

</script>
</body>
</html>

Solution 2:

JQuery has the each() method: http://api.jquery.com/jQuery.each/

but probably there's something similar even in other libraries such as Moo or Dojo.

Javascript 1.7 implements the Iterator function: https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Iterators_and_Generators


Solution 3:

This is my attempt (jsfiddle) for ECMAScript 262 5th edition (aka Javascript). (Uses for example Object.keys and Array.isArray)

//Usage
b=Iterator(a);
while(b()){
  console.log(b.value);
}

The code:

function Iterator(input,keys) {
  // Input:
  //  input : object|array
  //  keys   : array|undefined|boolean
  function my() {
    ++my.index;
    if (my.index >= my.keys.length) {
      my.index = my.keys.length -1;
      my.key = my.value = undefined;
      return false;
    }
    my.key = my.useIndex ? my.index : my.keys[my.index];
    my.value = my.input[my.key];
    return my.index < my.keys.length;
  }
  if (input === null || typeof input !== 'object') {
    throw new TypeError("'input' should be object|array");
  }
  if (
    !Array.isArray(keys)
    && (typeof keys !== 'undefined')
    && (typeof keys !== 'boolean')
    ) {
    throw new TypeError("'keys' should be array|boolean|undefined");
  }
  // Save a reference to the input object.
  my.input = input;
  if (Array.isArray(input)) {
    //If the input is an array, set 'useIndex' to true if 
    //the internal index should be used as a key.
    my.useIndex = !keys;
    //Either create and use a list of own properties,
    // or use the supplied keys
    // or at last resort use the input (since useIndex is true in that
    // case it is only used for the length)
    my.keys = keys===true ? Object.keys(input) : keys || input;
  } else {
    my.useIndex = false;
    my.keys = Array.isArray(keys) ? keys : Object.keys(input);
  }
  // Set index to before the first element.
  my.index = -1;
  return my;
}

Examples:

function Person(firstname, lastname, domain) {
  this.firstname = firstname;
  this.lastname = lastname;
  this.domain = domain;
}
Person.prototype.type = 'Brillant';

var list = [
  new Person('Paula','Bean','some.domain.name'),
  new Person('John','Doe','another.domain.name'),
  new Person('Johanna','Doe','yet.another.domain.name'),
];

var a,b; 
var data_array = ['A','B','C','D','E','F'];
data_array[10]="Sparse";


console.log('Iterate over own keys in an object, unknown order');
a = Iterator(list[0]);
while(a()) console.log("  ",a.key, a.value);

console.log('Iterate over keys from anywhere, in specified order');
a = Iterator(list[0], ['lastname','firstname','type']);
while(a()) console.log("  ",a.key, a.value);

console.log('Iterate over all values in an array');
a = Iterator(list);
while(a()) console.log(a.key, a.value.firstname, a.value.lastname);


//Some abusing, that works for arrays (if the iterator.keys is modified
//it can also be used for objects)
console.log('Add more entries to the array, reusing the iterator...');
list.push(new Person('Another','Name','m.nu'));
while(a()) console.log(a.key, a.value.firstname, a.value.lastname);

console.log('Reset index and print everything again...');
a.index=-1; //Reset the index.
while(a()) console.log(a.key, a.value.firstname, a.value.lastname);

//With arrays, if setting 'keys' to true it will only print the
//elements that has values (If the array has more own enumerable values
//they too will be included)
console.log('Print sparce arrays...');
a = Iterator(data_array,true);
while(a()) console.log(a.key, a.value);

Solution 4:

In the time since this question was asked JavaScript has added actual Iterators. Some built-in types, such as Array, Map, and String now have a default iteration behavior, but you can add your own to any object by including a next() function which returns one of two objects:

{done:true}     /*or*/
{done:false, value:SOMEVALUE}

One way to access an object Iterator is with the:

for ( var of object ) { }

loop. Here is a (reasonably silly) example where we define an Iterator and then use it in such a loop to produce a string 1, 2, 3:

"use strict";

function count ( i ) {
  let n = 0;
  let I = {};
  I[Symbol.iterator] = function() {
     return { next: function() { return (n > i) ? {done:true}
                                                : {done:false, value:n++} } } };
  let s = "";
  let c = "";
  for ( let i of I ) {       /* use the iterator we defined above */
      s += c + i;
      c = ", "
  }
  return s;
}


let s = count(3);
console.log(s);

Solution 5:

Ive used LINQ to Javascript in a few projects.

http://jslinq.codeplex.com/Wikipage

var myList = [
            {FirstName:"Chris",LastName:"Pearson"},
            {FirstName:"Kate",LastName:"Johnson"},
            {FirstName:"Josh",LastName:"Sutherland"},
            {FirstName:"John",LastName:"Ronald"},
            {FirstName:"Steve",LastName:"Pinkerton"}
            ];

var exampleArray = JSLINQ(myList)
                   .Where(function(item){ return item.FirstName == "Chris"; })
                   .OrderBy(function(item) { return item.FirstName; })
                   .Select(function(item){ return item.FirstName; });

Post a Comment for "JavaScript Iterator Class"