HTML5, CSS3, jQuery, JSON, Responsive Design...

A better way to pass parameters to JavaScript functions

Michael Brown   September 7 2011 03:31:06 AM

The traditional way to pass arguments/parameters to a function is by a list of individual elements, like so:
function doFruitColour(banana, apple,  tangerine) {
     apple = apple || "green";                // These OR statements will set defaults for missing parameters
     banana = banana || "yellow";
     tangerine = tangerine || "orange";

console.log(args);
}


You would then call such a function like this:
doFruitColour("yellow", "red");


This call will result in the following:
apple = red
banana = yellow
tangerine = orange


While this works fine for functions that take only two or three parameters, it quickly becomes unworkable for functions that take more arguments than that.  Managing the list becomes a major problem in that parameters must be passed into the function in the correct order.  Optional parameters may be dropped in JavasScript (but not LotusScript), but only if they follow the mandatory arguments in the list order.  If you want to omit a parameter that appears before a parameter that you need to include, then you must replace the omitted parameter with some kind of null value, and then handle that situation within the function itself (as I have done with the || statements above).  Note that in the example above, I was able to drop the tangerine parameter, but not the banana parameter, even though I was only passing in the default value anyway.  If I'd supplied nothing at all for banana, then it would picked up the value for apple, and we would have ended up with a red banana!

A much better to way to pass in the parameters is to pass in an object instead.  (In his book, Javascript: The Good Parts, Douglas Crockford calls this an "Object Specifier", although I've also seen it referred to as a "Configuration Object").

This time, I'll show the function call first:
doFruitColour({"apple":"red"});


As you can see, it's passing in a single object.  Further more, that object contains only one parameter: the "apple" parameter.  The other parameters have been omitted, and in this case, the order of the supplied and omitted is not important because in the function we refer to them by their name rather than by their position in the list.  Here's the modified function:
function doFruitColour(args) {
     args.apple = args.apple ||"green";
     args.banana = args.banana || "yellow";
     args.tangerine = args.tangerine || "orange";

console.log(args);
}


My doFruitColour({"apple":"red"}) call will again give the results:
apple = red
banana = yellow
tangerine = orange

Although this works, and is infinitely preferable to the first function, the way it handles setting up the defaults is still somewhat verbose.  There's a lot of || statements to set up: one for each parameter, in fact.  It would be nice to able to able to set up a single, default arguments object and then || the entire parameters object against tis defaults object, e.g:
function doFruitColourNew(args) {
// Do not use.  This doesn't work!!
var argsDefault = {
     apple :"green",
     banana :"yellow",
     tangerine :"orange"};
args = args || argsDefault;
}


where args and argsDefault are both objects.  Sadly, this results in one object replacing the other object completely rather than working at the object's element level, as we would like.  The end result of another call doFruitColour({"apple":"red"}) call would end up with the apple set to "red" and the other two parameters not initialised at all; the args parameter passed into the function take total precedence.

Fortunately, the jQuery.extend() function does exactly what we want, ie., it merges two objects at their elemental level.  Here's a reworked function that used $.extend to merge the actual arguments and default argumetns which are now defined in an object of their own:
function doFruitColourNew(args) {
var argsNew = $.extend({
     apple :"green",
     banana :"yellow",
     tangerine :"orange"
}, args) ;

console.log(argsNew);
}


The $.extend() call merges the two objects at an element level, with the elements in second object listed (args in the example above) taking precedence over those in the first object.  However, the elements in the first object are still returned where there are no corresponding elements in the second object.

Yet again, a doFruitColour({"apple":"red"}) call will again give the desired results:
apple = red
banana = yellow
tangerine = orange


If you're not using jQuery (you poor, benighted soul!) then here's a native JavaScript function that would achieve the same result:
function mergeObjects(obj1, obj2) {
  // merges two objects, returning all objects in obj1 unless those same elements are also in obj2
  // in which case, the elements in obj2 will take precedence.  This is the equivalent of jQuery.merge().
  obj1 = obj1 || {};
   obj2 = obj2 || {};
   returnObj = {};

 for (var index in obj1) {
     returnObj[index] = obj2[index] || obj1[index];
 }
 return returnObj;
}


It loops through all the elements of the default arguments object, and fills out the actual arguments objects elements for any of the latter that are "missing".  Here's a modified version of our example function that makes use of the processArgsWithDefaults() function:
function doFruitColourNew(args) {
 args = mergeObjects({
     apple :"green",
     banana :"yellow",
     tangerine :"orange"
}, args) ;

 console.log(args);
}

Comments
No Comments Found

About