Default Arguments in JavaScript Functions
I often run into people who need (for reasons that are beyond me) to be able to give arguments in a javascript function default values. Kinda like what you have in C/C++:
void foo(int a, int b = 42) { ... }
I felt an urge to do it, so I sat down for a few minutes, trying to conjure something nifty that would be intuitive enough for even me to use.
The solution I came up with, and that hopefully will put an end to the I-need-default-arguments-in-javascript rant for good.
The solution is quite simple, and very intuitive. It may have some shortcomings but keep in mind I didn’t explicitly write it for you. You could probably modify my solution to fit your needs anyway.
I’ve seen alot of developers (including myself, long time ago) using this pattern as a workaround for the lack of built-in support for default-arguments.
function foo(a, b)
{
a = typeof(a) != 'undefined' ? a : 42;
b = typeof(b) != 'undefined' ? b : 'default_b';
...
}
Which most developers probably find sufficient, I don’t.
The solution may look scary at a quick first glance, but bear with me. I’ll start by writing the framework-code. The code that will be required for this thing to work.
Function.prototype.defaults = function()
{
var _f = this;
var _a = Array(_f.length-arguments.length).concat(
Array.prototype.slice.apply(arguments));
return function()
{
return _f.apply(_f, Array.prototype.slice.apply(arguments).concat(
_a.slice(arguments.length, _a.length)));
}
}
See that wasn’t so scary :).
In order for this to work you have to declare you functions in a special way. There are basicly two ways of declaring a function.
function foo(a, b)
{
...
}
Is identical to (apart from the lexical difference, and some other minor things)
var foo = function(a, b)
{
...
}
In order for this solution to work you have to declare all functions on which you wish to have default-arguments the latter way.
Usage
var foo = function(a, b)
{
...
}.defaults(42, 'default_b');
Is identical to the first code-block but without adding any code in the function-body.
Example
var bar = function(a, b)
{
}.defaults('default_b');
bar();
// a = undefined, b = 'default_b'
bar(1);
// a = 1, b = 'default_b'
bar(1, 'some_value');
// a = 1, b = 'some_value'
on June 16th, 2006 at 3:55 pm
Okay, I’ll bite… for what reason do “others” claim they need the defaults, defined as part of the parameters?
I can see, if they are “used” to this, that they would like to continue doing something like this, but the…
if( typeof(a) == ‘undefined’ ){
a = 37;
}
or what you originally showed works just fine for me.
Steve
on June 30th, 2006 at 1:38 pm
It’s a pretty handy function, there will be few times when I’ll use it, but I’ll save it for those times. I would like to have seen a little comments in the code, I got it after looking through it a few times, but most people probably won’t bother trying to understand it. Using something you don’t understand is really really bad.
Steve: Yes it works fine to do typeof, but I see no reason to not use fatbrain’s function now that it’s available. Also, there is no way for to detect whether undefined was passed as the value of the argument or if it was omitted when using typeof, while it won’t be a defined property of the arguments object if the argument wasn’t passed.
on July 3rd, 2006 at 2:36 pm
Raeval, I got to correct you: When you submit “undefined” as value for the parameter, typeof will most likely return “string” as datatype.
on July 5th, 2006 at 7:49 am
haha that is way cool! i just found this other site that had:
function(arg1,arg2)
{
var def_arg1 = arg1 || ‘DEFAULT VALUE’;
}
although i can’t dispute the coolness of your function, i also think that all that extra process to save no code space seems redundant
on July 21st, 2006 at 1:49 pm
white: If you mean that passing the string “undefined” will result in typeof(arg)===”string”, that was obvious and not what I was talking about.
If you meant passing undefined, you’re incorrect; (function (x) { return typeof(x); })(undefined) // => “undefined”
on July 25th, 2006 at 5:25 am
Quietly nested in that method is a nice bit of sugar.
Array.apply(null, arguments)
Lovely approach for passing argument objects to .apply without manually iterating over the object.
Immediately useful, thanks a million.
on March 22nd, 2007 at 6:45 am
Wow–only six comments and I have three things to respond to. First of all, thanks so much, fatbrain, for publishing this. It would be useful if it weren’t ClearSilver that I actually needed to have default arguments for.
Iain, the scrap of code you present runs into the danger Raevel is (correctly) talking about: if you pass that function zero or false or anything else JS evaluates as false, it sticks in the default value, even if what you really wanted to pass was zero, false, etc. Bad news. Default arguments are hugely helpful when you decide to add an argument to a function to expand its functionality for a special case but don’t want to have to pick back through your code to fix its previous performance for all the other times you’ve called it. For simple JS it’s totally gratuitous, but for more complex stuff it’s a great time-saver and error-catcher. Finally, I have to say that I cheerfully use code that I don’t understand frequently. While the controlling part of my nature is apt to agree that it’s bad, I think that part is ultimately wrong: if this is something successful and tested, I don’t have to understand anything about it but how to use it.
on April 25th, 2007 at 11:34 am
Thank You
on May 5th, 2007 at 2:48 am
Hi.. I loved your idea, I was trying to create something like this for a while but couldn’t figure out how.. basing it on your code, I created a similar function that causes less troubles (no errors if the function.length
on May 7th, 2007 at 8:37 pm
I added a “minor than” symbol in the previous post and the post is not showing properly, I’ll repost it without the symbol.
fatbrain, I loved your code, I was trying to code something like this but I couldn’t, so I searched and found yours, I modified it a bit so that it won’t throw an error if the defaults array is larger than the arguments one. Also will update parameters that are submitted as null or undefined.
All this changes are not much relevant.. the only problem I found is that you were passing _f as scope of the Function.apply.. instead I think it should be receiving “this” to behave as expected. here’s my version.
Function.prototype.defaults = function(){
var f = this;
var def = Array.prototype.slice.apply(arguments);
function update(a){
var na = Array.prototype.slice.apply(a);
for(var i=0;i
on June 29th, 2007 at 1:19 pm
In Javascript, I use 3 different methods to get this effect…
Optional Function Arguments in JavaScript
Your method is the fourth - Thanks!
on June 29th, 2007 at 6:56 pm
I’ve read hundreds of articles containing js snippets for accomplishing various things, most of which are either no-brainers or completely irrational.
This one is not. Well done.
on July 2nd, 2007 at 12:07 pm
Nice idea and elegant implementation, although I think I am not going to use it because:
1. it is less readable than the “if typeof” version or the shorter “= .. || ..”, because the defaults are seperated (lines of code wise) far from the argument list.
2. if you add another argument to the function you have to update the default list, which might lead to hard to find processing errors. Maybe one could work around by requiring that the default call always get’s the same number of arguments as the function itself expects, causing an error if it doesn’t.
on July 6th, 2007 at 11:16 am
Thanks for all your lovely comments, very much appreciated :-)
on July 13th, 2007 at 8:04 am
I have a hybrid idea that uses your method with associative arrays:
http://www.adventsun.com/code/JS_Function_Defaults.htm
Thanks for pointing me in the right direction! :)
on September 17th, 2007 at 4:07 pm
Your way won’t work with objects.
This way will: http://phpfi.com/263389
Regards
e-voc
on September 17th, 2007 at 4:30 pm
sorry, the last on was buggy.
here’s the working one:
Function.prototype.defaults = function(o) {
var _f = this;
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var a = Array.prototype.slice.call(arguments);
return _f.apply(o, a.concat(args.slice(a.length)));
}
}
function Foo() { this.bla = 1; }
Foo.prototype.foo = function(a, b) {
return this.bla + ‘:’ + a + ‘/’ + b;
};
var f = new Foo;
f.foo = f.foo.defaults(f, 42, ‘default_b’);
f.foo(10, 20); // returns “1:10/20″
f.foo(10); // returns “1:10/default_b”
f.foo(); // returns “1:42/default_b”
on September 17th, 2007 at 4:34 pm
For normal functions, one would pass null or window as execution context
var bar = function (who) { return who; }.defaults(null, ‘me’);
bar();
Regards
e-voc
on October 25th, 2007 at 5:58 pm
Thanks for input e-voc. What if we consider:
Function.prototype.defaults = function()
{
var _f = this;
var _a = Array(_f.length-arguments.length).concat(
Array.prototype.slice.apply(arguments));
return function()
{
return _f.apply(this, Array.prototype.slice.apply(arguments).concat(
_a.slice(arguments.length, _a.length)));
}
}
Or did I get it wrong? Anyhow, give it a testspin and get back to me.
Damn I’m hungry, need food for brain. (I only change the “this” argument in the apply on the inner function)
Cheers // fatbrain