Adam
Adam

Reputation: 4227

Javascript convention for variable length arguments

I am getting more in to javascript development, and want to ensure I am following popular conventions. Currently I have a library which consists of functions that can be passed either 1 model to operate on, or many models.

Given the climate that a few javascript libraries are very popular, I am curious; would I be conforming to the 'defacto standard' by achieving my 'single-item or list-of' requirement, by enumerating the arguments variable, or by allowing one of the arguments to be an array?

Scenario 1: argument enumeration

// passing a single entity to my function
sendMail( email, recipient1 );

// passing multiple entities to my function
sendMail( email, recipient1, recipient2 );

Scenario 2: entity argument is either single instance, or array

// pass a single entity
sendMail( email, recipient1 );

// passing multiple entities
sendMail( email, [recipient1, recipient2] );

I have seen areas of jQuery which use 'scenario 2', but I would still like to ask - which approach is the most popular, and why?

Thanks

[EDIT]

A couple of comments have followed the same vein, of using an arguments object - which is similar to 'scenario 2' - but I feel it introduces unnecessary complexity - the elements dont need to be named, because they are just a variable length list. I thought I would just add that here in case my question wasn't clear enough.

[EDIT]

I see code like this all through jQuery-1-7.js

queue: function( elem, type, data ) {
    var q;
    if ( elem ) {
        type = ( type || "fx" ) + "queue";
        q = jQuery._data( elem, type );

        // Speed up dequeue by getting out quickly if this is just a lookup
        if ( data ) {
            if ( !q || jQuery.isArray(data) ) {
                q = jQuery._data( elem, type, jQuery.makeArray(data) );
            } else {
                q.push( data );
            }
        }
        return q || [];
    }
}

[EDIT]

After some discussion with JP, I came up with this - which I'm not saying is the right choice, but it is very flexible...

lastArgumentAsParams: function()
{
    var callerArgs = jQuery.makeArray(this.lastArgumentAsParams.caller.arguments);

    // return empty set if caller has no arguments
    if ( callerArgs.length == 0 )
        return [];
     callerArgs.splice(0, callerArgs.length - 1)
    // remove all but the last argument
    if ( callerArgs.length == 1 && jQuery.isArray(callerArgs[0]))
        return callerArgs[0];
    else
        return callerArgs;
}

If you call this function at the beginning of any function - it will treat the last arg in the caller as a 'variable length argument' - supporting any of the conventions.

For example, I can use it like this

function sendEmail( body, recipients )
{
    recipients = lastArgumentAsParams();

    // foreach( recipient in recipients )...
}

Now, I can call 'sendEmail' in any of the following ways and it will work as expected

sendEmail('hello world', "[email protected]" );
sendEmail('hello world', "[email protected]", "[email protected]" );
sendEmail('hello world', ["[email protected]", "[email protected]"] );

Upvotes: 9

Views: 5561

Answers (3)

hugomg
hugomg

Reputation: 69934

To expand on the other answers, there are two main alternatives I usually see: optional arguments and keyword arguments. I don't remember seeing any good examples of the "array-using" idiom and it is kind of obsolete given how the arguments array is always available anyway.

Anyway, my rule of thumb is.

  • If I have many arguments, or the argument list is likely to change, or if the arguments don't have a good natural order, use the named arguments pattern

    My favorite part about this style is that it is really flexible and future proof, while also being kind of self-documenting (in a smalltalk style).

    foo({x1:'1', x2:'2', x3:'3'});
    
    function foo(kwargs){
        //I try to always copy the arguments back into variables.
        //Its a little verbose but it helps documentation a lot and also
        // lets me mutate the variables if I want to
    
        var x1 = kwargs.x1,
            x2 = kwargs.x2,
            x3 = kwargs.x3;
    }
    
  • If I have few arguments, that are not likely to change, and have a natural order to them, use a plain function (with the optional arguments last in the order)

    foo(x1, x2);
    foo(x1, x2, x3);
    

    There are three main variations I can think right now of how to handle the optional arguments in the function:

    var foo = function(x1, x2, x3){
    
         //variation 1: truthy/falsy
         // Short, but I tend to only use it when the variable stands
         // for an object or other always-truthy kind of value
         x3 = x3 || 'default_value';
    
         //variation 2: using a special placeholder value for blank arguments.
         // Usually this is null or undefined. (and undefined works if the arg is not passed too)
         if(typeof x3 === 'undefined'){ x3 = 'default_value'; }
    
         //variation 3: explicitly check the number of arguments
         // I really like this one since it makes clear if the argument was passed or not.
         if(arguments.length < 3){ x3 = 'default_value'; }
    }
    

Also, there are so things I try to avoid:

  • Don't have functions that receive a large argument list. It can become a mess if they start becoming optional and you forget the order

    foo(1, 2, null, null, 3, null, null); //ugh
    
  • Don't use fixed-length arrays to be tricky. They are redundant with no arrays at all and when I see an array I usually expect it to 1) be homogeneous and 2) be able to be as long as I want to

    foo(true, [1, 2]); //should be foo(true, 1, 2)
    

Upvotes: 2

JP Richardson
JP Richardson

Reputation: 39395

I personally prefer using object literals for arguments to support named params, like this:

var myfunc = function(params){ //same as: function myfunc(params){....
  alert(params.firstName);
  alert(params.lastName);   
};

myfunc({firstName: 'JP', lastName: 'Richardson'});

I think that it makes code very readable and order won't matter.

OR

You can also access the arguments object. Note, it's not an array, but it's "array-like". You can read about it here: http://javascriptweblog.wordpress.com/2011/01/18/javascripts-arguments-object-and-beyond/

Edit:

You seem to have a misunderstanding here. You're using the phrase "arguments object" and are thinking that it's the same as object literal notation. They are not.

The arguments object allows you to do this:

function myfunc(){
  alert(arguments[0]); //JP
  alert(arguments[1]); //Richardson 
}

myfunc('JP', 'Richardson');

Does that help?

Upvotes: 7

u.k
u.k

Reputation: 3091

Another common way is to use object literal as variables:

myFunction(true, {option: value, option2: value});

I personally prefer this method for it is more verbose, and with javascript loose types, it gives you a better hint for what this variables is, and ignores order.

Backbone.js is using this as the preferred method.

Upvotes: 2

Related Questions