JavaScript is a more powerful ProgrammingLanguage than many people give it credit for. After being intrigued by BlocksInRuby, I decided to try my hand at implementing blocks, LexicalClosures, HigherOrderFunctions, and AnonymousFunctions in JavaScript.
JavaScript functions are FirstClassFunctions in that they are objects which can be easily passed around, thereby enabling a FunctionalProgramming style, if desired. AnonymousFunctions or LambdaExpressions can be created either with the function keyword or the Function constructor. The Function constructor allows you to define the body of the function dynamically.
For illustration, I have taken a few examples from the on-line version of ProgrammingRuby (the PickAxeBook) and transformed them into JavaScript. Already knowing PythonLanguage helped me make the transition to JavaScript.
//////////////////////////////////////// // handy functions to help with browser output function dwrb(thing) { if (arguments.length == 1) document.writeln(thing + '<br>'); else document.writeln('<br>'); } function dwrs(thing) { if (arguments.length == 1) document.writeln(thing + ' '); else document.writeln(' '); } //////////////////////////////////////// function fibUpTo(max, block) { var i1=1, i2=1; while (i1 <= max) { block(i1); var tmp2 = i2; i2 = i1 + i2; i1 = tmp2; } } fibUpTo(1000, dwrs); dwrb(); // produces 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 // LexicalClosure function into(anArray) { return function(val) { anArray[anArray.length] = val; } } var a = ['foo', 'bar']; fibUpTo(20, into(a)); dwrb(a); // produces foo,bar,1,1,2,3,5,8,13 //////////////////////////////////////// // HigherOrderFunction added to built-in Array type Array.prototype.find = function(block) { for (var i=0; i<this.length; i++) { var item = this[i]; if (block(item)) return item; } return null; } var arr = [1, 3, 5, 7, 9]; // AnonymousFunctions dwrs(arr.find(function(v) { return v > 30; })); // produces null dwrs(arr.find(function(v) { return v*v > 30; })); // produces 7 dwrb(arr.find(function(v) { return v*v*v > 30; })); // produces 5 //////////////////////////////////////// // HigherOrderFunctions added to built-in Array type Array.prototype.inject = function(n, block) { for (var i=0; i<this.length; i++) { n = block(n, this[i]); } return n; } Array.prototype.sum = function() { function helper(n, value) { return n + value; } return this.inject(0, helper); } Array.prototype.product = function() { function helper(n, value) { return n * value; } return this.inject(1, helper); } var arr = [1, 2, 3, 4, 5]; dwrs(arr.sum()); // produces 15 dwrb(arr.product()); // produces 120 //////////////////////////////////////// // LexicalClosure (CurryingSchonfinkelling) function nTimes(aNum) { return function(n) { return aNum * n; } } var p1 = nTimes(23); dwrs(p1(3)); // produces 69 dwrb(p1(4)); // produces 92 //////////////////////////////////////// function TaxCalculator(name, block) { this.name = name; this.block = block; this.getTax = function(amount) { return this.name + ' on ' + amount + ' = ' + this.block(amount); } } // AnonymousFunction var tc = new TaxCalculator('Sales tax', function(amt) { return amt * 0.075; }); dwrb(tc.getTax(100)); // produces Sales tax on 100.00 = 7.50 dwrb(tc.getTax(250)); // produces Sales tax on 250.00 = 18.75 //////////////////////////////////////// // HigherOrderFunction added to built-in Array type Array.prototype.collect = function(block) { var a = Array(); for (var i=0; i<this.length; i++) { a[i] = block(this[i]); //or a.push(block(this[i])); } return a; } var times = prompt('(t)imes or (p)lus:', 'times'); var number = prompt('number:', 1); var cancel = false; if (times == null || number == null) cancel = true; if (!cancel) { number = parseInt(number); if (!isNaN(number)) { // function definition based on user input function funcbody(sign) { return 'return n ' + sign + ' number;'; } if (times.search(/^t/i) > -1) calc = new Function('n', funcbody('*')); else calc = new Function('n', funcbody('+')); var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; dwrb(arr.collect(calc)); // might produce 4,8,12,16,20,24,28,32,36,40 } }The above examples are known to work in MozillaFirefox, SafariBrowser, InternetExplorer 5.2, and ancient NetscapeNavigator 4.75. If your IE is >= 5.5, you can use the Array.push method in the "into" and "collect" functions.
Beware of circular references in closures when targeting InternetExplorer. If a DOM object is involved it will produce a memory leak due to the bizarre JavaScript interpreter design. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp
JavaScript Array objects in MozillaFirefox 1.5 will implement some new methods: every(), some(), forEach(), filter(), and map(). Each of these Array methods takes as its parameter a function of the form
function func_name(value, index, array) { // code goes here }If you don't use Firefox, you can simulate its new Array.filter method, for example, by adding a function to the Array prototype:
if (!Array.filter) { // HigherOrderFunction added to built-in Array type Array.prototype.filter = function(predicate) { var a = []; for (var i=0; i<this.length; i++) { if (predicate(this[i], i, this)) a[a.length] = this[i]; } return a; } }The new Array.filter method is easy to use:
function isOver90(value, index, array) { return value > 90; } var aNumbers = [56, 43, 23, 94, 32, 91]; var aOver90Numbers = aNumbers.filter(isOver90); document.write("The numbers over 90 are " + aOver90Numbers + "<br>");Mozilla's new Array.filter method (and the others) can also take an optional second parameter, which I have not shown here.
See http://www.webreference.com/programming/javascript/ncz/column4/ and http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array
This article illustrates more uses of higher order functions in JavaScript:
See also JavaScriptRocks, BlocksInManyLanguages