Mike Slinn
Mike Slinn

JavaScript Named Arguments and Class Constructors

Published 2021-02-11.
Time to read: about 3 minutes.

This article is categorized under ECMAScript, JavaScript

Named arguments make a program safe from errors caused by changes to method arguments. JavaScript named arguments can appear in any order. Default values for parameters allow an API to evolve gracefully without runtime errors.

Building on the article entitled Cool JavaScript 9: Named arguments — Functions that get and return Objects, this article shows how JavaScript class constructors can use named arguments, optionally define default values for parameters, and conveniently inflate new class instances from JSON.

In this article I use Node.js for convenience, however the code shown will run in all modern web browsers.

JavaScript Class Definition Encapsulating Properties

Let’s quickly review how to define a JavaScript class and instantiate an instance. Here is a simple JavaScript / ECMAScript 6 class that encapsulates two properties: id and parts. The constructor merely lists the names of the parameters, which happen to be the same as the names of the class properties.

Shell
$  js
Welcome to Node.js v12.18.2.
Type ".help" for more information.
> class Ingredient {
...   constructor(id, parts) {
.....     this.id = id;
.....     this.parts = parts;
.....   }
... }
undefined 

New Ingredient instances can be created using this familiar syntax:

> var ingredient = new Ingredient("123", 10);
undefined 
> ingredient
Ingredient { id: '123', parts: 10 } 

Object Literals

Object literals look like JSON objects, but without quotes around property names. For example, the following defines an object literal called lit with 2 properties, called id and parts, with values "123" and 10, respectively.

>  var lit = {id: "123", parts: 10};
undefined 
$ lit
{ id: '123', parts: 10 } 
> lit.id
'123' 
> lit.parts
10 

Use Object Literals to Define Arguments

We can define a class similar to Ingredient, but with the arguments replaced by a something that looks like an object literal without values. For want of a better term I call this an object name literal. The following class definition encapsulates the same two properties as before as an object name literal.

> class IngredientX {
...   constructor({id, parts}) {
.....     this.id = id;
.....     this.parts = parts;
.....   }
... }
undefined 

New IngredientX instances can be created from an object literal:

> var ingredientX1 = new IngredientX({id: "123", parts: 10 });
undefined 
> ingredientX1
IngredientX { id: '123', parts: 10 } 

Because the IngredientX class definition requires an object name literal (or a JSON object, more on that later) to provide constructor arguments, constructor invocations must specify the names of each parameter being passed to the constructor arguments. This has the benefit of making your software more robust in the face of changing method signatures.

Caution: new IngredientX instances cannot be created from scalar arguments. JavaScript gives no error or warning if you do not:

> var ingredientX2 = new IngredientX("123", 10);
undefined 
> ingredientX2
IngredientX { id: undefined, parts: undefined } 

JSON Object Can Be Supplied Instead of Object Literals

JSON objects can be provided as arguments instead of object literals. This is extremely handy. Replacing several arguments with a JSON object would possibly be the most significant improvement in robustness that could be made to a JavaScript project. The number of runtime errors encountered as a code base evolves would be greatly reduced.

> var ingredientX3 = new IngredientX({
...     "id": "123",
...     "parts": 10
...  });
undefined 
> ingredientX3
IngredientX { id: '123', parts: 10 } 

Arguments and Parameters Can Be Provided In Any Order

This definition of ingredientX4 is identical to the definition of ingredientX3, even though the order of the arguments has been reversed:

> var ingredientX4 = new IngredientX({
...     "parts": 10,
...     "id": "123"
...  });
undefined 
> ingredientX4
IngredientX { id: '123', parts: 10 } 

The parameters in the function or method declaration are also insensitive to ordering:

> class IngredientXReordered {
...   constructor({parts, id}) {
.....     this.parts = parts;
.....     this.id = id;
.....   }
... }
undefined 
> var ingredientX5 = new IngredientXReordered({
...     "parts": 10,
...     "id": "123"
...  });
undefined 
> ingredientX5
IngredientXReordered { id: '123', parts: 10 } 

Object Literals Can Be Used With Any Method

Object literals / named arguments can be used to define the signature of any function or method, not just class constructors. For example:

> class IngredientY {
...    constructor({id, parts}) {
.....      this.id = id;
.....      this.parts = parts;
.....    }
... 
...    mix({duration, intensity}) {
...      console.log(`Shake for ${duration} hours at intensity ${intensity}.`);
...    }
...  }
undefined 
> var ingredientY = new IngredientY({id: "123", parts: 10 });
undefined 
> ingredientY.mix({duration: 2.5, intensity: 2});
Shake for 2.5 hours at intensity 2. 
undefined 

Default Values for Named Arguments

To make this example more interesting, the default value for id will be generated as a GUID. Here are some other GUID implementations, but the best implementations have dependencies and that would just make the article more complex than necessary.

> function uuidv4() {
...   return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
...     function(c) {
.....     var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
.....     return v.toString(16);
.....   });
... }
undefined 
> uuidv4()
'b13137c1-1598-42ca-9498-c1502e5405ed' 

A JavaScript object literal or JSON object must be passed to a method whose parameters were defined by object literal names. If a name/value pair is not provided in the argument then the default parameter value is used. Some examples should help demonstrate how this works:

> class IngredientZ {
...    constructor({id=uuidv4(), parts=10}) {
.....      this.id = id;
.....      this.parts = parts;
.....    }
... 
...    mix({duration=1.2, intensity=6}) {
...      console.log(`Shake for ${duration} hours at intensity ${intensity}.`);
...    }
...  }
undefined 
> var ingredientZ1 = new IngredientZ({parts: 4});
undefined 
> ingredientZ1
IngredientZ { id: '4290dc1a-4f4c-4579-9e27-39b68085ad97', parts: 4 } 
undefined 

Empty objects are allowed as arguments. All this means is that default values are used for all parameters of the object name literal.

> var ingredientZ2 = new IngredientZ({});
undefined 
> ingredientZ2
IngredientZ { id: '9e70dc12-1f4c-3579-6a17-49a68385bf73', parts: 10 } 
> ingredientZ2.mix({});
Shake for 2.5 hours at intensity 2. 

Missing objects result in a syntax error.

> ingredientZ2.mix();
Uncaught TypeError: Cannot read property 'id' of undefined
    at new IngredientZ2 (repl:3:17)
    {% noselect undefined 

For More Information

For more information, please see JavaScript for impatient programmers (ES2021 edition).