New Solutions to Old JavaScript Problems: 2) Default Values

Tim Brock / Monday, May 09, 2016

Introduction

This is the second in a series on how new JavaScript features, introduced in the ECMAScript 2015 standard (aka ES6), allow for simpler solutions to some old JavaScript problems. In part 1 I covered block scope and the new let and const keywords. Here I will look at default arguments for functions.

Default Arguments the Old Way

Here's a very simple JavaScript function for logging a greeting message to the console:

let greet = function(name){
   console.log("Hi, I'm " + name);
}

And here's two examples of calling that function:

	
greet("Elvis Presley"); //Hi, I'm Elvis Presley
greet(); //Hi, I'm undefined

Since the second call to greet doesn't pass in any arguments, the call to console.log just outputs name as "undefined". We can add a more appropriate fallback by using the logical OR operator, ||, inside the function and supplying a suitable moniker:

let greet = function(name){
   name = name || "John Doe";
   console.log("Hi, I'm " + name);
}

greet("Elvis Presley"); //Hi, I'm Elvis Presley
greet(); //Hi, I'm John Doe

Sometimes the use of || doesn't work as intended. Here's the skeleton of a function for plotting the mathematical function y = x2 + 1 using IgniteUI. (I've deliberately left out important aspects like chart labels to keep the code to a minimum).

let makeChart = function($selector, min, max){
		
   "use strict";		
		
   min = min || -5;
   max = max || 5;

   let stepSize = (max-min)/10000;
   let data = [];

   for(let i=min; i≤max; i=i+stepSize){
      data.push({x:i, y:Math.pow(i,2)+1});	
   }

   $selector.igDataChart({
      dataSource: data,
      width: "300px",
      height: "200px",
      axes: [
         {
            name: "xAxis",
            type: "numericX",
            minimumValue: min,
            maximumValue: max,
         },
         {
            name: "yAxis",
            type: "numericY",
            minimumValue: 0
         }
      ],
      series: [
         {
            name: "series1",
            type: "scatterLine",
            xAxis: "xAxis",
            yAxis: "yAxis",
            xMemberPath: "x",
            yMemberPath: "y",
         },
      ],
   });
}

Assuming three floating (left) div elements — with id's of "chart1", "chart2" and "chart3" — we might draw three copies of the function like this (here $ is the jQuery object):

makeChart($("#chart1"));
makeChart($("#chart2"), -3, 3);
makeChart($("#chart3"), 0);

The final results may comes as a surprise: rather than the x axis starting at 0, the third chart is identical to the first.

This happens because 0, the value passed in for the minimum value of the x axis (min) in the third call to makeChart, is falsy. That means the line

min = min || -5;

changes min to -5.

We can get around this easily, but with a few more keystrokes, by explicitly comparing min (and max) to undefined, rather than checking for truthyness:

min = min!==undefined ? min : -5;
max = max!==undefined ? max : 5;

With this in place, the third chart does now have an x axis that starts at 0.

More Intuitive Default Arguments

ES6 makes setting defaults easier and the new syntax may seem familiar if you've ever programmed in C++ or R. Simply add an equals sign and the default value for each function argument that needs one. The makeChart example above then becomes:

let makeChart = function($selector, min=-5, max=5){
   
   "use strict";

   let stepSize = (max-min)/10000;
   let data = [];

   /*rest of the function*/
   
}

In an ES6 compliant browser, this is equivalent to the example above, where we compared min and max to undefined, but with less typing. I think it also makes the code more transparent and a casual user no longer has to inspect the function body in order to find the default values.

The greet function from earlier can also be simplified:

let greet = function(name="John Doe"){
   console.log("Hi, I'm " + name);
}

greet("Elvis Presley"); //Hi, I'm Elvis Presley
greet(); //Hi, I'm John Doe

More Complex Examples

ES6 default values don't have to be simple strings or numbers. You can use any JavaScript expression and later arguments can refer to earlier ones.

One common way to introduce object-oriented programming to new coders is through the construction of various animal classes and objects. JavaScript isn't really a class-based language but objects are still important. Object factories - functions that return objects - are an easy way to make a large number of objects quickly. They're also a place where default arguments can be very useful. Let's create some talking dog (!) objects. The following is all perfectly valid ES6 code that utilizes default values (the defaults for name are, apparently, the most popular male and female dogs names as listed here at the time of writing):

let createDog = function(gender="male", name=(gender==="male")?("Bailey"):("Bella"), hobby="barking"){
   return {
      name: name,
      gender: gender,
      hobby: hobby,
      sayName: function(){console.log("Hi, I'm " + this.name); return this;},
      sayGender: function(){console.log("I am a " + this.gender + " dog"); return this;},
      sayHobby: function(){console.log("I enjoy " + this.hobby); return this;},
   };
};

Here's an example of use with the default values:

createDog().sayName().sayGender().sayHobby();

This prints out...

Hi, I'm Bailey
I am a male dog
I enjoy barking

We could specify all arguments, for example...

createDog("female","Barbara","fetching").sayName().sayGender().sayHobby();

prints out...

Hi, I'm Barbara
I am a female dog
I enjoy fetching

We can also accept default values for one or more parameters by passing in undefined. For example...

createDog("female",undefined,"fetching").sayName().sayGender().sayHobby();

leads to the output

Hi, I'm Bella
I am a female dog
I enjoy fetching

Note that this does mean you can't pass in undefined as a placeholder when you don't know the true value. You probably want to use null instead.

The expression for the default name isn't particularly robust. For example, the following is probably not what was intended:

createDog("Male").sayName().sayGender().sayHobby();
Hi, I'm Bella
I am a Male dog
I enjoy barking

In this case gender was set to "Male", which doesn't match "male", thus the default name expression results in a "Male" dog called Bella. We could just change the first argument to isMale (or isFemale of course) and assume a Boolean input. And that might be the sensible option here. But it doesn't lend itself so well to highlighting the fact that we can use even more complex expressions, so we won't. Instead we'll use the following modified function:

createDog = function(gender="male", name=(gender.charAt(0).toUpperCase()==="M")?("Bailey"):("Bella"), hobby="fetcing"){
   return {
      /*object unchanged*/
   };
}

Now any gender string beginning with "m" or "M" is taken to be male for the purposes of default-name assignment. That could mean "male", "Male", "man", "m", "M", or "Marmalade". Any other string — "female", "Female", "JavaScript" etc — will set name to "Bella" if you don't provide an alternative. This version will also throw an error (a good thing, generally) if you pass in something really silly for gender, like {} or /male/.

It's important to restate that ES6 default values are assigned from the left. It may be that you'd prefer name to be the first argument of the createDog function. The following won't work when called without a name parameter because the expression for the default tries to use gender before it is defined.

let createDog = function(name=(gender.charAt(0).toUpperCase()==="M")?("Bailey"):("Bella"), gender="male", hobby="reading"){
   return {
      /*object unchanged*/
   };
}

If you come to JavaScript from a language like Python or R then you may be used to using named parameters alongside default arguments in your function calls. This is still not possible in JavaScript. The closest you can do is to use an options object in place of multiple arguments. Unfortunately, combining an options object with ES6-style default parameters is not particularly easy (see here for details).

Now with full support for Angular 2 Beta and Bootstrap 4, see what's new in Ignite UI 16.1. Download free trial today.