barbarian meets coding

WebDev, UX & a Pinch of Fantasy

Mastering the Arcane Art of JavaScript-mancy for C# Developers - Chapter 4: More Useful Function Patterns - Multiple Arguments

| Comments

The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.

Time for some more useful function patterns in JavaScript!

In this article I’ll show you how to handle multiple parameters – the equivalent to params in C# – in the current version on JavaScript (ES5) and in the upcoming ECMAScript 2015 that brings great support for multiple parameters with the rest parameter syntax.

JavaScript-Mancer Level 90

A Pattern For Using Multiple Arguments In Functions Today

You can experiment with all these examples in examples in JsFiddle.

JavaScript gives you a lot of freedom when it comes to handling function parameters and arguments. For instance, you can declare a function with a specific number of parameters and call it without arguments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function heal(person, inchantation, modifier){
    var healedHp;
    modifier = modifier || 1;
    healedHp = modifier * 100;

    console.log('you heal ' + person + ' with ' + inchantation + ' spell (+' + healedHp + 'hp)' );
    person.hp += healedHp;
}
try{
    heal();
} catch (e) {
    console.log(e)
}
// => you heal undefined with undefined spell (+100hp)
// => typeError: cannot read property hp of undefined

Or with as many arguments you want:

1
2
3
4
5
6
7
8
// or with as many arguments as you want
var JonSnow = {name: 'Jon Snow', hp: 5, toString: function(){return this.name;}};
heal(JonSnow);
// => you heal Jon Snow with undefined spell (+100hp)
heal(JonSnow, 'cure');
// => you heal Jon Snow with cure spell (+100hp)
heal(JonSnow, 'super heal', /* modifier */ 2);
// => you heal Jon Snow with super heal spell (+200hp)

Or even with more arguments than the parameters defined in the function:

1
2
heal(JonSnow, 'heal', 1, 3, 2, 3, 'cucumber', {a: 1, b:2}, 'many arguments');
// => you heal Jon Snow with heal spell (+100hp)

It is up to you to implement the body of a function and handle each case as you see fit. But what happens when you want to write a function that takes an arbitrary number of arguments? In C# whenever we want to write such a function we use the params keyword. In JavaScript on the other hand, and as of ES5, there’s no keyword nor operator that allows us to declare that a function will take an arbitrary number of arguments and handle it within the function body in a seamless manner.

The only approach possible is to use the arguments object that is present within every function in JavaScript and which provides access to the arguments used to call a function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Taking a variable number of parameters in JavaScript:
// In C# we use params
// In JavaScript we take advantage of the arguments object
// available within every function
function obliterate(){
    // Unfortunately arguments is not an array :O
    // so we need to convert it ourselves
    var victims = Array.prototype.slice.call(arguments, /* startFromIndex */ 0);
    victims.forEach(function(victim){
        console.log(victim + " wiped out of the face of the earth");
    });
    console.log('*Everything* has been obliterated, oh great master of evil and deceit!');
}

obliterate("John Doe", getPuppy(), 1, new Minion('Willy', 'troll'));
/*
John Doe wiped out of the face of the earth
cute puppy wiped out of the face of the earth
1 wiped out of the face of the earth
Willy the troll wiped out of the face of the earth
*Everything* has been obliterated, oh great master of evil and deceit!
*/

function getPuppy(){
    return {
        cuteWoof: function(){console.log('wiii');},
        toString: function(){return 'cute puppy';}
    };
};

function Minion(name, type){
    this.name = name;
    this.type = type;
    this.toString = function(){ return name + " the " + type;};
}

There are a couple of specially interesting things to point out in this example.

The first one is the fact that we are sending arguments of different types to the function and they are all treated seamlessly. That’s an example of duck typing in action where an object is defined by what it can do and not by what the object type is. As long as the values that we pass as arguments support the interface the function implementation expects – in this case the toString method – everything will work just fine.

The second thing to point out is the use of the Array.prototype.slice function to convert the arguments variable into a real array victims. While you could think that arguments is an array, it is not (this is another of JavaScript quirks right here), it is an array-like object that you can index, enumerate, and that has a length property:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function inspectArguments(){
    console.log("the first argument is ", arguments[0]);
    console.log("the second argument is ", arguments[1]);
    console.log("there are " + arguments.length + " arguments");

    // the arguments are enumerable like
    // any common array or object, and thus you could use
    // the for/in loop
    for (var idx in arguments) {
       console.log("item at position " + idx + " is " + arguments[idx]);
    }
}
inspectArguments("cucumber", "dried meat", "dagger", "rock");
// => the first argument is cucumber
// => the second argument is dried meat
// => there are 4 arguments
// => item at position 1 is cucumber
// => etc...

But that does not have any of the array functions that you would expect:

1
2
3
4
5
6
7
8
9
function inspectArgumentsFunctions(){
    console.log("arguments.foreach is ", arguments.foreach);
    console.log("arguments.map is ", arguments.map);
    console.log("arguments.some is", arguments.some);
}
inspectArgumentsFunctions();
// => arguments.foreach is undefined
// => arguments.map is undefined
// => arguments.some is undefined

Using the slice function of Array.prototype allows you to convert arguments to an array and take advantage of all the nice array functions. This is what we did with the obliterate function within the first example:

1
2
3
4
5
6
7
8
function obliterate(){
    //...
    var victims = Array.prototype.slice.call(arguments, /* startFromIndex */ 0);
    victims.forEach(function(victim){
        console.log(victim + " wiped out of the face of the earth");
    });
    //...
}

Native Multiple Arguments with ECMASCript 2015 With Rest Parameters

ES2015 (ES6), the upcoming new version of JavaScript, comes with a native way to handle multiple arguments: rest parameters.

All the complexities of using the arguments object in the previous examples can be substitued by rest parameters and handled in a much seamless fashion:

1
2
3
4
5
6
function obliterate(...victims){
    victims.forEach(function(victim){
        console.log(victim + " wiped out of the face of the earth");
    });
    console.log('*Everything* has been obliterated, oh great master of evil and deceit!');
}

When using the rest parameters syntax, the victims parameter becomes an array so there’s no need to perform additional conversions.

You can test the example above on this jsFiddle snippet.

Concluding

In this article you learned about the flexibility of handling parameters and arguments in JavaScript, how to implement functions that can handle an arbitrary number of arguments in JavaScript today (in a similar way to how params works in C#). You also learned that in the upcoming version of JavaScript – ES 2015 – you will be able to use the rest parameters syntax that works exactly like params in C#.

Interested in Learning More JavaScript? Buy the Book!

Are you a C# Developer interested in learning JavaScript? Then take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.

a JavaScriptmancy sample cover

References

A Sidenote on Using Array.prototype.slice On Arguments

While I was writing this article I found out that there’s a drawback when using Array.prototype.slice to convert arguments in a array: It prevents the browser JavaScript engine from performing optimizations.

The recommended approach is to use Array.slice generic method – Array generic methods are specially implemented to be used on other types – but it is not a part of the ECMAScript specification (not even of ES6/2015) and is only implemented in certain browsers (try testing Array.slice in the web developer console on Chrome, Firefox and Internet Explorer and you’ll see that it only exists in Firefox).

You have two options:

  • use a polyfill that falls back to Array.prototype.slice when there are no generic functions available
  • ignore the recommendation based on the fact that functions with an arbitrary of arguments are uncommon :)

This problem will take care of itself when we start using the rest parameter syntax.

More Articles in These Series

Comments