In last week's Weekly Web Hack, I showed how to make recursive anonymous JavaScript functions. This week, I'll demonstrate how to unroll a recursive DOM traversal function into a potentially less expensive, non-recursive form.
One common reason to use-case for recursive JavaScript functions is DOM tree traversal. For example, consider the method Element.collectTextNodes from Scriptaculous's effects.js library. This method traverses all of a DOM node's descendents and collects the contents of text nodes into a single concatenated string.
The source for this method (as of Scriptaculous version 1.8.0) appears here:
Element.collectTextNodes = function(element) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); }).flatten().join(''); };
Despite looking a little complex, the code is actually pretty straightforward. It does the following:
Now, here is an alternative implementation, which does not use recursion:
Element.collectTextNodes = function(element) { var toscan = $A($(element).childNodes), results = []; for (var i=0; i<toscan.length; i++) { var node = toscan[i]; if (node.nodeType==3) { results.push(node.nodeValue); continue; } toscan.splice(i, 1); var j = i--; $A($(node).childNodes).map( function(item) { toscan.splice(j++, 0, item); } ); } return results.join(''); };
Unlike the first example, this one is a bit longer and abstains from a good deal of the Prototype/Scriptaculous "magic" featured in the former.
Nevertheless, it's also fairly easy to understand - it does the following:
You'll notice that this second example breaks one of the ivory rules of looping - only alter the loop variable in one place, and only in one direction. Of course, the trade-off here is that by abusing this tenant, we get to abandon recursion.
Whether or not this will have any real impact on performance is to be seen. It may not make a difference, and the non-recursive call may even be slower in practice. In either case, I hope these examples are instructive.
Recursion is an important concept to understand in software development, but it can become an expensive crutch if used improperly. After all, the curve is a circle.
Enjoy! As always I'll be happy to answer any questions.
Got something to say?
or, read what others have said...