Post Archive
› January 11, 2006
JavaScript Function Arguments: Assigning Defaults
This article demonstrates a nice technique for setting default values for function arguments in JavaScript. The basic form of the idiom is:
function foo(a, b, c) {
var b = (b == null) ? "default" : b;
var c = (c == null) ? 100 : c;
...
}
Within the body of a function, the argument b will receive the value of "default" if no corresponding value is passed to the function. This statement is repeated for each argument requiring a default value.
Not being one to leave well enough alone, I wanted to "improve" on this idea by creating a function to simplify the assigning of these defaults. Is this an improvement? You be the judge.
First, here is the function:
1. function defaults(passed) {
2. var pattern = /function[^(]*\(([^)]*)\)/;
3. var args = passed.callee.toString().match(pattern)[1].split(/\s*,\s*/);
4. var str = "", i = 1;
5. for ( ; i < arguments.length; i++) {
6. if (typeof passed[i-1] == "undefined") {
7. str += args[i-1] + "=" + fix(arguments[i]) + ";";
8. }
9. }
10. return str;
11.
12. function fix(x) {
13. if (typeof x == "string")
14. return "'" + x.replace(/\'/g, "\\'") + "'";
15. return x;
16. }
17. }
And here is how to use it:
function foo(a, b, c) {
eval(defaults(arguments, 1, 2, 3));
alert(a); // defaults to 1
alert(b); // defaults to 2
alert(c); // defaults to 3
}
The defaults function requires the first argument to be the arguments object of the enclosing function (in this case, foo). The rest of the arguments are the default values for the corresponding arguments passed to foo.
The defaults function bears some explaining:
- The calling function (
foo) is turned into a string, and its argument list is extracted (lines 2,3) - The arguments passed to
fooare examined (line 5). These are contained in the first argument passed todefaults, which is theargumentsobject offoo(confusing isn't it?) - If any of these arguments are
null, meaning no value was passed, the default value is assigned (lines 6,7) - Assigning a variable at this point would make it local to the
defaultsfunction. We want it to be in the scope offoo. In order to do this, we need to return the assignment as a string thenevalit (cue groaning from certain JavaScripters). For instance, iffoois called with no arguments,defaultswill return:"a=1;b=2;c=3;". If it is called with one argument,defaultswill return"b=2;c=3;", and so on - The call to
evalinfooeffectively assigns the arguments with their default values - The nested function
fixensures that strings are handled properly. Sincedefaultsreturns a string to beevaled, string assignments need to be wrapped in quotes and have any nested quotes escaped.
Some shortcomings, real or perceived:
- Only primitive types (numbers, booleans) and strings are handled
- Uses
evalwhich some people dislike - Might not be that much of an improvement over the "canonical" way of assigning defaults
It might not lend itself to some people's coding style or philosophy, but if you find yourself setting default values often in your functions, this technique offers a clean, consistent way of doing it.
Comments
1. January 11, 2006 03:49 PM
2. January 11, 2006 05:00 PM
Patrick Fitzgerald Posted…
I'm not sure if I'm missing something, but I find it easier to use an object to pass arguments, which has the added advantage of making the arguments optional and position-independent.
myfunction({ c:5, a:22 });
function myfunction(passedArgsObj) {
/* defaults */
args = { a:1, b:2, c:3 };
/* override the defaults if necessary */
for (var argName in passedArgsObj) {
args[argName] = passedArgsObj[argName];
}
alert("a = "+args.a);
alert("b = "+args.b);
alert("c = "+args.c);
}
3. January 12, 2006 04:12 AM
Vincenzo Posted…
If a parameter is not passed, it has a default value of undefined, not null. So you should check for the undefined value.
As someone already mentioned in Dan's blog, javascript performs implicit conversion when the == operator is used.
0 == null false == null undefined == null
This is why, the check for null is true if the parameter is not passed. But it is wrong, because if you pass an explicit value of 0, it will also evaluates to true.
You have to use, one of these comparisons:
a) passed[i-1] === undefined b) typeof passed[i-1] == "undefined"
4. January 12, 2006 10:20 AM
Dave Posted…
Thanks Vincenzo for catching that. That is correct. I have updated the function.
5. January 15, 2006 05:02 AM
lon Posted…
My approach would be to enhance the original function by re-creating it holding the required default code.
So: serialize it, enhance it, re-create it
That way you don't need the evil eval, variables are in the correct scope and there is no extra function call.
6. February 8, 2006 02:06 AM
patrick Posted…
Why not using :
function foo(a, b, c) {
var b = b || "default";
var c = c || 100;
...
}
7. February 23, 2006 03:54 PM
Runsun Pan Posted…
The form:var b = b || 'default'
is dangerous. Anything that is evaluated "false" will be converted to 'default':
function f(a,b){
a = a || "aaa"
b = b || true
return [a,b]
}
alert(f()) ==== > got expected ['aaa', true]
alert(f(4,5)) ====> got expected [4,5]
alert(f(0,false)) ===> expected [0, false], but got ['aaa',true]
8. June 14, 2006 09:55 AM
Matthias Miller Posted…
Why not use a variation of Patrick Fitzgerald's solution?
Function.prototype.defaults = function(origArgs/*, defaults*/) {
for (var i = 0, j = 1; j < arguments.length; i++, j++) {
if (typeof origArgs[i] == 'undefined')
origArgs[i] = arguments[j];
}
};
function foo(a, b, c) {
this.defaults(arguments, 1, 2, 3);
alert(a); // defaults to 1
alert(b); // defaults to 2
alert(c); // defaults to 3
}
Menno Posted…
It looks like this would result in global variables being declared. Since function parameters are always local, you might want to change line 7 to the following: