9

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]"] );
4
  • Note: again: the "arguments object" as you state, is different than both my answer and Uzi's answer. Commented Mar 6, 2012 at 20:46
  • I've edited my answer to hopefully clear up confusion. Commented Mar 6, 2012 at 20:48
  • I would prefer if we didn't confuse the named-object pattern with variable length lists. Variable length lists make me think of things like printf, that can take any number of arguments, while keyword arguments are great for optional arguments and arguments that can change order. Anyway it would probably be best if JS were like Python, where you can mix and match the two styles. Commented Mar 6, 2012 at 21:22
  • @missingno - I totally agree, the two are completely different concepts. I see named-objects as 'optional arguments' where each argument is explicitly referenced by the function code, could be of different type, and have completely different symantic meaning. Variable-length-lists would typically be referenced by the function code through some kind of loop, where each item were expected to share the same basic schema, and was treated semantically the same. Commented Mar 6, 2012 at 21:30

3 Answers 3

7

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?

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks JP - but an arguments object wouldnt make sense, because I dont want to pass a 'flexible structure of items, where each item has a difference semantic context'. Each 'variable item' has the same meaning. i.e. allowing both "mailServer.send( email1 );" and "mailServer.send( email1, email2 );" But I am guessing you would probably prefer my 'scenario 2'.
@Adam Note arguments object and object literals are orthogonal concepts. If there was one parameter, then I wouldn't use object literals. If there is more than one parameter, most of the time, I prefer object literals. To be 100% clear, I'm not a fan of your "scenario 2".
Also, your scenario 2 isn't necessary. Because you can declare a function without any parameters and pass as many as you like. See my link on the arguments object.
Thanks JP. I'd agree with that. I come from a C# background, so scenario 1 makes most sense to me - but then I saw the latter used in jQuery... Say more than one argument needs to be sent, and they both have the same semantic meaning (which is the case in my question) - then surely object literals gets in the way, because you have to give each literal-property a unique name? I suppose one could use: myfunc({0: 'Johnson', 1: 'Richardson'}); // note how they are both last-name - that is intentional But isn't that unnecessarily verbose? Arrays automatically add the '0' and '1'.
"Also, your scenario 2 isn't necessary" Absolutely! Thats exactly what I think too - but I have seen it done that way in jQuery code. I want to make sure that whatever way I do it, it makes sense to a developer who is up to date with commonly accepted javascript conventions...
|
2

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.

2 Comments

Thanks Uzi - your answer appears to be the same as JP's - Dont you think this approach introduces unnecessary complexity? Both the caller and the callee need to be aware that the naming convention 'option1: X, option2: Y' needs to be used. Using var-args or an array ([]) removes the need to 'name' each entry.
I'm not sure how it ads complexity. If you think about it the same way of declaring a variable type, than it doesn't add much complexity, and it does add a lot of verbosity which can be an issue dealing with a loosely typed language.
2

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)
    

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.