Coderrob brand logo Coderrob

Hi, I'm Rob. I'm a programmer, Pluralsight author, software architect, emerging technologist, and life long learner.


Javascript Function Parameters and Arguments Explained


Thu, 17 Sep 2020

JavaScript Function Signatures

Alright, you’ve probably seen functions before. Something like this maybe?

    function doSomething(name, desc, task) {
        // did something
    }

This function, named doSomething, takes three parameters as part of its function signature. These parameters named name, desc, and task are the names of the values being sent into the function.

I’m going to use a sportsball reference here so be warned. You can think of the function as a net that gets various types of objects tossed into the hoop ( ) where it triggers sports points.

    let points = 0;

    function basket(ball) {
        // +3 points to Gryffindor!
        points += 3;
    }

JavaScript functions may have no parameters, one parameter, or pretty much as many as you want to define on the function.

Using sportsball again, like a basketball hoop that is meant to take just one parameter, or like skee-ball where you just toss in as many objects as you can.

What is a parameter you may or may not be asking yourself?

A parameter is simply the name used to represent the value being passed in. Kind of like a variable that’s defined for you as part of the function itself.

Refactor Rob: If a function takes three or more parameters it is worth evaluating whether the function should be refactored to use an object with expected properties instead. Skee-ball functions bad m’kay.

Calling JavaScript Functions

Now, here is where JavaScript goes off the rails compared to many other languages.

Just because JavaScript functions define parameters in the function signature does not mean that the parameters are required when the function is called.

Wat.

Using the doSomething example from earlier there were three parameters defined.

All three are required to make the function call, right? Seems reasonable.

Nope, the following function calls are all perfectly valid. Not saying the results would be valid, but the function calls are.

    // Eh, one should do it
    doSomething('tots valid call');

    // Not even pretending to try here
    doSomething();

Not only can you pass less than the parameters defined in the function signature, but you can actually pass more values than the function expects too!

    let name, desc, task, bogo;

    // Three was a crowd, but four is a PARTY ... parameter party!
    doSomething(name, desc, task, bogo);

At this point you’re starting to realize something about JavaScript function signatures…

JavaScript Pirate Code

OK, so since JavaScript violates the laws of the function pyhsics (that’s a thing right?) how can we be sure that anything is getting passed to our functions when we need it?

Besides inspecting the parameter values directly there is a neat but mostly unknown object called arguments.

You know, not to be confused with the actual concept of parameters and arguments, but an actual object available within a function.

We’ll hold off on dealing with our arguments in a minute. First we need to see how to protect ourselves from this parameter shenanigans.

JavaScript Function Parameter Defences

To lighten the ol’ cogntive load let’s look back at the doSomething function:

    function doSomething(name, desc, task) {
        // did something
    }

Now that we know that we can’t trust any of these parameters we need to look at how we can do some defensive programming.

I won’t dive too deeply into what parameter types can or cannot be. All parameters values share the following rules in JavaScript.

The simple way to combat this situation is an equality check.

    function doSomething(name, desc, task) {
        // First parameter check
        if (name == null) {
            // Who are you?
            return;
        }
    }

Notice, the quality check is not === because null does equal the type undefined, but null is NOT the same type as undefined.

Don’t believe me? Fair. Try this in the console:

console.log(typeof null);
//object

console.log(typeof undefined);
//undefined

Now, what if some dilligent refactorer comes along blindingly passing out extra equality = to every == check without actually verifying if the change breaks something.

Refactor Rob: Yes, this does happen. A “friend” of mine, who will remain anonoymous, told me he’s done this before, and he’s very sorry. He hopes you forgive me him.

So, if we do a name == null equality check this will tell us two things.

But what if the value of name was NaN or an empty string ''? We’d have to check if the value was null, undefined, NaN, or an empty string too.

Here is another thing about Javascript. JavaScript logic checks are like a politician’s statement. They may have different definitions of truth in them.

In JavaScript you have either truthy or falsy values.

:smh:

Right, so, JavaScript variables may or may NOT be what we think they are.

To help us figure out the quantum conundrum of JavaScript values there is an even simpler approach to defending against this madness.

JavaScript Truthy and Falsy Evaluations

Behold, the magic of variable truthy or falsy checks!

    function doSomething(name, desc, task) {
        // Is name a thing or not?
        if (!name) {
            // You're not what I was expecting.
            return;
        }
    }

What magic is this?

This, my internet friends, is the magic of evaluating the truthy or falsy-ness of a JavaScript variable’s value.

What this did under the covers was to check if the name parameter’s value was any of the following falsy values:

If the name parameter’s value was equal to any of these values the name value would be considered falsy.

What !name does is inverts the logic and checks if name is NOT a truthy value.

The way to think about this is like this:

    if (!name) {
        // name is a falsy value
    }

    if (name) {
        // name is a truthy value
    }

At this point we now know how to defend against missing parameter values, or at least how to evaluate them enough to know if they’re missing when we require them.

But what if in the future we refactor doSomething from requiring three parameters down to two?

Before:

    function doSomething(name, desc, task) {
    }

After:

    function doSomething(name, task) {
    }

We know that JavaScript doesn’t care how many values you provide in the function call, right? Function signatures are just a guideline remember?

Don’t worry, yes, there is a way to handle this if you cannot refactor ALL the function calls in your application to downsize the parameters.

This, my confused friends, is where the JavaScript function arguments comes in.

JavaScript arguments Object

If you haven’t read anything up to this point, we’ve just had an intrepid developer refactor our doSomething function, reducing the required parameters from 3 to 2.

    function doSomething(name, task) {
    }

Me: “Great job! All the references have been updated right?”

Me2: “Well, it’s possible not all the references were found. It is JavaScript after all…"

Me: “Ugh, OK, let’s patch the function to support the previous signature. Also, you’re fired.”

Me2: “OK… I deserved that."

OK, now we need to be backwards compatible with the previous function signature that took three parameters.

To do that, JavaScript has a nifty object that is scoped to the function called arguments.

Check Number of Parameters Passed to Function

To support backwards compability (or simply to satify curiosity), we can quickly check how many parameter values were sent in a function call. Let’s take a look at an example.

    function lights(one, two, three) {
        const count = arguments.length;
        console.log(count);
    }

    lights(1, 2, 3, 4);

What would the console output?

Picard knows. Let’s ask him.

Picard counting code

Exactly Picard. Our function only expected three parameters, but when called it had four values sent to it. The arguments object contains references to the values that were sent to the function in the order they were sent.

This is incredibly useful.

Let’s take a look at what the arguments object looks like when we call our lights. Engage.

function lights(one, two, three) {
    console.log(JSON.stringify(arguments));
}

lights(1, 2, 3, 4);
// {"0":1,"1":2,"2":3,"3":4}

What happens if I change the value of one within the function? Does it change the arguments[0] value?

Yes, yes it does.

Changing arguments[0] would also change the value of the one parameter. The arguments holds references to the values not a copy of the data.

function lights(one, two, three) {
    one = 8;
    console.log(JSON.stringify(arguments));
}
//{"0":8,"1":2,"2":3,"3":4}

function lights(one, two, three) {
    arguments[0] = 8;
    console.log(JSON.stringify(one));
}
// 8

I think you get the point.

With this new found programmatic alchemy how would we fix Mr. Refactors backwards compability issue with the doSomething function?

    function doSomething(name, desc, task) {
    }

Well, one option would be to use the arguments, check the number of values sent, and if we received 3 values it’s likely the old function signature being used.

    function doSomething(name, task) {
        // old function signature detected - most likely
        if (arguments.length === 3) {
            // we didn't need a description to do something
            task = arguments[2]
        }
        // keep calm, and carry on with doing something
    }

    // Missed reference call somewhere in the code...
    doSomething('chores', 'Things I should be doing.', () => {});

Now, there is a danger in relying on arguments. Especially with Captain Refactor roaming free.

Our mystery JavaScript refactorer just recently finished reading about arrow functions, and now the world points towards refactoring ALL the functions.

We come back from lunch to find that doSomething has been updated recently to an arrow function variable.

const doSomething = (name, task) => {
    // old function signature detected - most likely
    if (arguments.length === 3) {
        // guess we didn't need a description anyway
        task = arguments[2]
    }

    // carry on with doing something
}

doSomething('Chores', 'Things I should be doing.', () => {});

Still works, right? No. No, it does not…

The arguments object does not exist with arrow functions.

Refactor Rob: Before refactoring make sure to create unit tests that will verify existing behavior. This is especially important because any change could lead to breaking existing behavioral contracts.

On that note. I hope you enjoyed my post on JavaScript functions and arguments explained.

Later topics will touch on this, that, and other programming related topics.