The Javascript language has, over the last few years, evolved dramatically, to the extent that even those who are regular Javascript developers are likely to not fully appreciate all that can be done with the web’s most ubiquitous language. In this particular article, I hope to concentrate on the realm of conditional expressions. Most Javascript developers are familiar with the typical if
then
else
keywords, but oftentimes, especially when working with template literals, array iterators, and similar structures, the use of conditional statements can be cumbersome.
Using Simple Conditional Expressions
A conditional expression, then, is condensed syntax that will return one expression if a particular expression is true, and another if that condition is false. The most basic such expression makes use of the ?: notation. For instance, let’s say that if the tempC (temperature in Celsius) value is below
const tempC = 12;
const t1 = (tempC>0)?"hot":"cold";
> t1: "hot"
This expression allows you to replace and if/then/else
block with a single line of code, assigning the value ‘hot’ to the corresponding t1 variable.
Admittedly, this is a very coarse test, especially a 1 C (about 34 degrees Fahrenheit) is hardly “hot”. One advantage of the conditional expression, however, is that it can also be chained:
const t2 = (tempC > 30) ? "hot": (tempC >20) ? "warm": (tempC >10) ? "mild": (tempC >0)?"cool":"cold"
> t2: "mild"
This chaining process can be extended indefinitely, with the false response being replaced by a conditional expression. It can even be wrapped into a function via a lambda (or arrow) operator:
const tempName = (tempC) => (tempC > 30) ? "hot": (tempC >20) ? "warm": (tempC >10) ? "mild": _
(tempC >0)?"cool":"cold"
tempName(25)
> "warm"
One other useful feature of conditional expressions is that you can break expressions into multiple lines after each of the operators. Thus, the above can be rewritten as
const tempName = (tempC) =>
(tempC > 30) ? "hot":
(tempC > 20) ? "warm":
(tempC > 10) ? "mild":
(tempC > 0) ?"cool":
"cold"
making it much clearer what the criteria are for each conditional phrase.
It’s worth noting that what’s being passed does not have to be a string. It could just as readily be a function. For instance, suppose that you wanted a function that returned the square of a number between 0 and 1, the square root of a number if greater than 1, and 0 if the argument was less than 0, which we’ll call squiggle. The function itself might look like this:
const squiggle = (x) => (x <0) ? 0: (x <1) ? Math.sqrt(x) : x * x
squiggle(-0.5)
> 0
squiggle(0.5)
> 0.70710678
squiggle(1.5)
> 2.25
Using Nullish Coalesce to Manage Nulls
In Javascript 2020, the Ecmascript working group introduced a couple of new kinds of conditionals. The first was known as the nullish coalesce operator, and is used primarily in those situations where you may have a variable that has a null value.
let nameObj = null
let name = nameObj? nameObj : "Jane Doe";
> name: "Jane Doe"
This is actually a situation that comes up occasionally when pulling from a dataset. The OR operator (||) is frequently used to make this a little terser:
let nameObj = null
let name = nameObj || "Jane Doe";
> name: "Jane Doe"
The OR operator, however, has a problem with it, in that the operator will automatically convert elements on either side to a Boolean value, which can be very problematic when working with numbers (0 or 0.000) or strings (“”), either of which may be legitimate values. In Ecmascript 2021, a new null coalesce operator (indicated by “??”) . Unlike the OR operator, the null coalesce operator will use the first term only if is not a null
or undefined
value.
This comes in handy in a number of ways: if a variable is declared but not yet defined (such as in a parameter), this can be used to set the value:
var x;
x = x ?? 1;
> x: 1
Navigating Javascript Paths with the Optional Chaining Operator
A similar operator, the optional chaining operator (given as “.?”, provides a solution to one of the more frustrating aspects of Javascript objects: what happens when you try to create a path that accesses something that isn’t there. For instance, let’s say that you have a Javascript object that contains a person and their cat, which has a name:
let janeRecord = {person:{name:"Alice", cat:{name:"Dinah"}}}
let catName = janeRecord.person.cat.name
> catName: "Dinah"
However, suppose that a person could have a cat or a dog. In the above code, replacing cat with dog will generate an error.
let dogName = janeRecord.person.dog.name
>>> VM1923:1 Uncaught TypeError: Cannot read properties of undefined (reading 'name')
You can always wrap this in a try/catch block, but this is a lot of overhead for what should be a simple query. The chaining operator solves this by returning null if the path to a given Javascript chain can’t be completed:
let dogName = janeRecord.?person.?dog.?name
> dogName: null
This could even be chained with a null coalesce operator:
let dogName = janeRecord.?person.?dog.?name ?? "Fido"
> dogName: "Fido"
In general, you only need to use an optional chain if you do not know whether a given tree node exists or not, but other than a slight performance hit there’s nothing keeping you from chaining everything.
Chain Gang
Chaining and conditional expressions are useful tools, especially in conjunction with both iterators such as maps and filters, and with template literals, primarily because they cut down on the amount of code needed to deal with nulls and undefined objects. Additionally, conditional expressions can be split up on multiple lines, providing a structure that is similar to a switch statement. The result is easier to read code that combines terseness with expressiveness, which is always a desirable state where Javascript is concerned.