The list of new features in JavaScript 1.7 have been out for a while but only since the move to Trunk (Gecko 1.9.1 codebase) have we taken a look into it.
The concept of iterators is nothing new, the way they are implemented in JavaScript initially confused me. Consider the following bits of code:
function range(begin, end) {
for (let i = begin; i++; i < end) {
yield i;
}
}
let it = range(10, 20);
What is this!? A function with a loop that doesn’t return anything and contains a strange statement called yield? Without much reading, my next though was: why does |it| contain anything? Shouldn’t |it| be undefined? The function doesn’t return anything, so why should yield make a difference?
I guess the return “issue” just takes a bit getting used to. Most iterator functions are very small, so |yield| should be easy to spot. Now I’d like you to see why this strange construct is indeed very useful. Consider this (old) bit of code. It was used when turning an ICS string into an item:
for (var attprop = icalcomp.getFirstProperty("ATTENDEE");
attprop;
attprop = icalcomp.getNextProperty("ATTENDEE")) {
var att = new CalAttendee();
att.icalProperty = attprop;
this.addAttendee(att);
}
A multi-line for() looks very cumbersome even aside from the fact that the different line lengths make it harder to read, don’t you think? Now lets see what magic we can do with iterators:
for (let attprop in cal.ical.propertyIterator(icalcomp, "ATTENDEE")) {
let att = cal.createAttendee();
att.icalProperty = attprop;
this.addAttendee(att);
}
You’ll notice a few things changed. The loop now uses the newly introduced property iterator and only one line (even though its a bit longer). You’ll also see use of the new “cal” namespace, which probably deserves its own blog post. While this may look like a minimal change please consider a lot of such for loops are used when turning an ICS string into an item. Also I feel this makes the formerly quite native code pattern (getFirstXXX, getNextXXX) fit more naturaly into JavaScript.
We can go even further, using more language features of JavaScript 1.7. This bit of code used in the same context:
this.mCategories = [];
for (var catprop = icalcomp.getFirstProperty("CATEGORIES");
catprop;
catprop = icalcomp.getNextProperty("CATEGORIES")) {
this.mCategories.push(catprop.value);
}
Can be compromised into a single line, using iterators as described above and array comprehensions:
this.mCategories = [ catprop.value for (catprop in cal.ical.propertyIterator(icalcomp, "CATEGORIES")) ];
I’ll admit this line is a bit long. I have found no sensible way to split it, comments welcome. You’re surely interested how to create something that can be used as an iterator so that you can use it in your own code. I’ll give you the source for the above cal.ical.propertyIterator:
function cal_ical_propertyIterator(aComponent, aProperty) {
return {
__iterator__: function icalPropertyIterator(aWantKeys) {
cal.ASSERT(aWantKeys, "Please use for() on the property iterator");
let propertyName = (aProperty || "ANY");
for (let prop = aComponent.getFirstProperty(propertyName);
prop;
prop = aComponent.getNextProperty(propertyName)) {
yield prop;
}
}
};
}
Looks quite similar to the old code, doesn’t it? The concept is quite simple. We return an object that implements the special __iterator__ method. This is where we put the old loop code. Then we “yield” the value that should be availibe in our new loop code. The argument aWantKeys differentiates if only the key is wanted or not. If the iterator is used in a for() loop, then aWantKeys is true. If it is used in a for each() loop, then the argument is false.
for (let key in myIterator()) {
// A for(...in..) loop goes through all keys in the array or iterator.
// The aWantKeys argument is true.
}
for each (let [key, value] in myIterator()) {
// Standard object iterators return an array with key and value
// when aWantKeys is false.
}
for each (let value in myOtherIterator()) {
// Some iterators prefer only returning the value in this case.
}
The above code is of course only an example. You are of course free to return anything you want in your iterator.
Note that you can use the __iterator__ on any object, it doesn’t have to be an object that has no other properties.
function calPropertyBag() {
this.mData = {};
}
calPropertyBag.prototype = {
mData: null,
__iterator__: function cpb_iterator(aWantKeys) {
for (let key in this.mData) {
yield (aWantKeys ? key : [key, this.mData[key]]);
}
/* In this case, instead of the above you can shorten this iterator to:
return Iterator(this.mData, aWantKeys);
*/
},
/* Also implement other methods like setProperty,deleteProperty,... */
}
See how this basic property bag now has an iterator that can be used directly? Without the iterator, using this code:
var bag = new calPropertyBag();
for (let key in bag) {
dump("Key: " + key + "n");
}
Would give you all keys in the object: mData, setProperty, deleteProperty and so on. With the new iterator, the above code would rather give you the actual properties in the bag, since the __iterator__ yields all keys in this.mData.
I hope this inspires you, regardless of if you are working on a Sunbird/Lightning extension, or any other piece of Javascript code for that matter. Please do feel free to correct me, I’m quite new to these features so not everything might be 100% correct.
If I come across more new langauges features, I’ll keep you posted!