22. May 2009, 14:09, by Leo Büttiker

From the for loop to the generator in JavaScript

I recently spent some time writing JavaScript code and do reviews of JavaScript features. JavaScript has some nice language features that make it easy to write short and readable code. (On the other hand it is also quite easy to write horrible code with it.) In this article I try to show you how you can refactor your code to something more readable.

We may start with this non-fictional example:

/**
* Returns array of uids of selected items
*/
getSelected:function() {
	var returnArray = new Array();
	var modelFriendListItems = this.modelFriendListItems.getSelected();

	for (var index = 0 ; index < modelFriendListItems.length ; ++index) {
		if(!modelFriendListItems[index].isFacebookUser)
			returnArray.push(modelFriendListItems[index].id);
	}

	return returnArray;
},

It’s just one function out of a class that does quite something typical. This function goes over a list of items (iterate) and builds an array out of some of this items (filter). A friend of mine does some ruby coding and in ruby this would look something like this:

this.modelFriendListItems.getSelected().select{|item| !item.isFacebookUser}.collect {|item| item[:id]}

This is quite short and still yet readable. In JavaScript you can go as well in the direction of more functional programming. So let us refactor the JavaScript example above over some iterations. The following examples use prototype.js because this framework extends the JavaScript array with some fancy functions.

First we replace the “new Array()” term with the shorter term ‘[]‘. Then we try to get rid of the for loop which is quite a easy place to create a bug. Prototype does extend each iterable object (array is one of them) with a set of functions. One of them is the “each” function which does apply the anonymous function given as a parameter to each element of the iterable.

/**
* Returns array of uids of selected items
*/
getSelectedFB:function() {
	var result = [];
	this.modelFriendListItems.getSelected().each(function(item) {
		if(item.isFacebookUser){ result.push(item.id);  }
	});
	return result;
},

This is in my point of view already easier to read and it might be harder to create bugs as in a for loop. I do not like that you have to define a result array ‘outside’ of the main function. So the following code will do exactly the same in a more recursive style. You give as a starting value a empty array to inject and the anonymous function does now have to parameter where the first one is the array with the values and the second one is again the current item. You have to return the array at the end of the function. This makes the code again a bit shorter.

getSelected:function() {
	return this.modelFriendListItems.getSelected().inject([], function(array,item) {
		if(!item.isFacebookUser){ array.push(item.id);  }
		return array;
	});
},

You get the code might get a bit more readable it you use two other functions of prototype. ‘findAll‘ does return a array of all items that do match the given filter function. ‘pluck‘ does return an array with the values of a given field from all items.

getSelected:function() {
	return this.modelFriendListItems.getSelected().findAll(funtion(item){
	return !item.isFacebookUser;}).pluck('id');
},

The only way, I can think of, to make this short and more readable would be to have the function ‘filterAllBy’ and ‘filterAllByNot’ in prototype. This is currently not the case, so the example bellow will not work.

getSelected:function() {
	return this.modelFriendListItems.getSelected().filterAllByNot('isFacebookUser').pluck('id');
},

Some people did ask me if this code is not slower. Well it is! During the iteration of refactoring you lose some speed, but that should usually not matter in a real world application. But I spend also some time on how you could make your code faster with smart functional programing. I found out that generators (the one from python) are quite a good way to save cpu time under some circumstances.

For this I made up this ridiculous example. It’s the same thats in the article about phyton in the german computer magazin c’t (“Neues aus der Schlangengrube”, c’t 5.2009, p. 198).

var squares = $R(1, 1000000).map(function(n) {
	return n * n;
});

var result = [];
squares.each(function(n) {
	if (n > 100) throw $break;
	result.push(n);
});

$("output").innerHTML += result;

Off course this example is horible slow and will nearly crash your browser. I measured something like 24 seconds on a IE 8, something around 6.5 seconds on a Firefox 3 and around 400 milliseconds on chrome. A lot of the numbers you generate into the array squares are never used. With a generator you only generate the numbers you actually need.

I actually implemented a generator for prototype.js. It’s quite easy to create a own enumerable in prototype you just have to extend enumberable and implement the ‘_each‘ method. I did this in the following example with the class Generator. I also did implement the method mapGen directly into the Enumerable, it does have the same function as prototype.

var Generator = Class.create();

[...]
 mapGen: function(iterator, context) {
		var result = new Generator(iterator, this);
		return result;
	},
[...]

Object.extend(Generator.prototype, Enumerable);
Object.extend(Generator.prototype, {

	initialize: function(generator, enumerable) {
		this.generator = generator;
		this.enumerable = enumerable;
	},

	_each: function(iterator) {
		this.enumerable.each(function(item){
			iterator(this.generator(item));
		}, this);

	},
});

The code looks afterwards still nearly the same:

var squares = $R(1, 1000000).mapGen(function(n) {
  return n * n;
});

var result = [];
squares.each(function(n) {
  if (n > 100) throw $break;
  result.push(n);
});

$("output").innerHTML += result;

The real difference is the execution time. As the first example took some seconds on the most browser, this code here runs in around 1 millisecond on all browsers. Nice looking code does not have to be slow, with some smart tricks you can write short, readable and fast code.

Filed under: PHP,Programming

3 Comments

  1. Seems like a nice exercise, but for the real world project i would still recommend jquery.

    It really does all of that …. its ready, tested, compatible … plus you don’t have to write/research anything.

    ps. used to use prototype and its bit messy, lots of code and pollutes prototype. You cant stop loving jquery once you use it …. you just cant.

    nice any way, cheers

    Comment by Artur — 25. May 2009 @ 10:35

  2. Thanks for your comment Artur. I did not looked to deep into jquery, but it looks like a nice framework.
    What I like about prototype is that it is very decupled from the task you do, jquery is more coupled to the gui and in my point of view it is also sometimes a bit “magic”.

    Comment by leo — 27. May 2009 @ 17:49

  3. thank!! Great Post!!!

    Comment by FeedsTech.com — 13. July 2009 @ 09:00

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

© 2014 tilllate Schweiz AG - Powered by WordPress