How best to determine if an argument is not sent to the JavaScript function

Solution 1:

There are several different ways to check if an argument was passed to a function. In addition to the two you mentioned in your (original) question - checking arguments.length or using the || operator to provide default values - one can also explicitly check the arguments for undefined via argument2 === undefined or typeof argument2 === 'undefined' if one is paranoid (see comments).

Using the || operator has become standard practice - all the cool kids do it - but be careful: The default value will be triggered if the argument evaluates to false, which means it might actually be undefined, null, false, 0, '' (or anything else for which Boolean(...) returns false).

So the question is when to use which check, as they all yield slightly different results.

Checking arguments.length exhibits the 'most correct' behaviour, but it might not be feasible if there's more than one optional argument.

The test for undefined is next 'best' - it only 'fails' if the function is explicitly called with an undefined value, which in all likelyhood should be treated the same way as omitting the argument.

The use of the || operator might trigger usage of the default value even if a valid argument is provided. On the other hand, its behaviour might actually be desired.

To summarize: Only use it if you know what you're doing!

In my opinion, using || is also the way to go if there's more than one optional argument and one doesn't want to pass an object literal as a workaround for named parameters.

Another nice way to provide default values using arguments.length is possible by falling through the labels of a switch statement:

function test(requiredArg, optionalArg1, optionalArg2, optionalArg3) {
    switch(arguments.length) {
        case 1: optionalArg1 = 'default1';
        case 2: optionalArg2 = 'default2';
        case 3: optionalArg3 = 'default3';
        case 4: break;
        default: throw new Error('illegal argument count')
    }
    // do stuff
}

This has the downside that the programmer's intention is not (visually) obvious and uses 'magic numbers'; it is therefore possibly error prone.

Solution 2:

If you are using jQuery, one option that is nice (especially for complicated situations) is to use jQuery's extend method.

function foo(options) {

    default_options = {
        timeout : 1000,
        callback : function(){},
        some_number : 50,
        some_text : "hello world"
    };

    options = $.extend({}, default_options, options);
}

If you call the function then like this:

foo({timeout : 500});

The options variable would then be:

{
    timeout : 500,
    callback : function(){},
    some_number : 50,
    some_text : "hello world"
};

Solution 3:

This is one of the few cases where I find the test:

if(! argument2) {  

}

works quite nicely and carries the correct implication syntactically.

(With the simultaneous restriction that I wouldn't allow a legitimate null value for argument2 which has some other meaning; but that would be really confusing.)

EDIT:

This is a really good example of a stylistic difference between loosely-typed and strongly-typed languages; and a stylistic option that javascript affords in spades.

My personal preference (with no criticism meant for other preferences) is minimalism. The less the code has to say, as long as I'm consistent and concise, the less someone else has to comprehend to correctly infer my meaning.

One implication of that preference is that I don't want to - don't find it useful to - pile up a bunch of type-dependency tests. Instead, I try to make the code mean what it looks like it means; and test only for what I really will need to test for.

One of the aggravations I find in some other peoples' code is needing to figure out whether or not they expect, in the larger context, to actually run into the cases they are testing for. Or if they are trying to test for everything possible, on the chance that they don't anticipate the context completely enough. Which means I end up needing to track them down exhaustively in both directions before I can confidently refactor or modify anything. I figure that there's a good chance they might have put those various tests in place because they foresaw circumstances where they would be needed (and which usually aren't apparent to me).

(I consider that a serious downside in the way these folks use dynamic languages. Too often people don't want to give up all the static tests, and end up faking it.)

I've seen this most glaringly in comparing comprehensive ActionScript 3 code with elegant javascript code. The AS3 can be 3 or 4 times the bulk of the js, and the reliability I suspect is at least no better, just because of the number (3-4X) of coding decisions that were made.

As you say, Shog9, YMMV. :D

Solution 4:

In ES6 (ES2015) you can use Default parameters

function Test(arg1 = 'Hello', arg2 = 'World!'){
  alert(arg1 + ' ' +arg2);
}

Test('Hello', 'World!'); // Hello World!
Test('Hello'); // Hello World!
Test(); // Hello World!

Solution 5:

There are significant differences. Let's set up some test cases:

var unused; // value will be undefined
Test("test1", "some value");
Test("test2");
Test("test3", unused);
Test("test4", null);
Test("test5", 0);
Test("test6", "");

With the first method you describe, only the second test will use the default value. The second method will default all but the first (as JS will convert undefined, null, 0, and "" into the boolean false. And if you were to use Tom's method, only the fourth test will use the default!

Which method you choose really depends on your intended behavior. If values other than undefined are allowable for argument2, then you'll probably want some variation on the first; if a non-zero, non-null, non-empty value is desired, then the second method is ideal - indeed, it is often used to quickly eliminate such a wide range of values from consideration.