Published 2021-02-11.
Time to read: 3 minutes.
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.
$ 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).