Exploring JavaScript's nullish and optional chaining operators

Exploring JavaScript's nullish and optional chaining operators

31 Jul 2023 nullish operators

ECMAScript version 11, which landed in 2020, gave us two new, super-useful operators to play with in JavaScript, known as the nullish and optional chaining operators. In this article I'll look at both of them and show how they're used.

The nullish operator 🔗

The nullish operator takes the form of a double question mark, ??, and is used in any expression to return the left or right side of the operator. The left hand is returned unless it is null or undefined (hence "nullish"), in which case the right side is returned.

let foo = window.bar ?? 'something'; //something

In the above example, foo is assigned the value "something" because the left-hand side of the operator evaluates to undefined. Let's see a few more examples:

let thisIsNull = null; let foo2 = thisIsNull ?? 'something'; //something let foo3 = 5 ?? 'something'; //5

There, foo2 is assigned the value "something" because thisIsNull evaluates to null. But in the final example, because the left  side of the operator is not nullish - it is 5 - foo3 is assigned the value 5, and the right side of the operator, "something", is ignored.

Differences vs. the logical OR operator 🔗

If you're familiar with JavaScript's logical OR operator (||), you might think this looks rather familiar. Indeed, all three of the examples above would work exactly the same if we swapped the nullish operator for the OR operator.

The difference, however, is that the OR operator returns the left side unless it is falsy. The nullish operator is more strict; it returns the left side unless it is nullish (null or undefined).

Falsy means any value which, when used in a conditional, evaluates to false. In JavaScript this means any of: null, undefined, 0, empty string or NaN (not a number).

There are a few other falsy values in JavaScript, but these are by far the most common.

Let's try an example where the two operators would yield different results:

let foo = 0 || 5; //5 - left side is falsy let foo2 = 0 ?? 5; //0 - left side is not nullish

With the first example, the result is the right side - 5 - since we're using the OR operator and the left side is falsy. With the second example, the result is the left side - 0 - since, while it is falsy, we're using the nullish operator and that is concerned only with nullish values, not falsy ones.

The optional chaining operator 🔗

Next up is the optional chaining operator, ?.. This is designed to reduce the amount of code needed to check the existence or value of a nested property in an object.

Consider the following:

const obj = null; if (obj.foo && obj.foo.bar && obj.foo.bar.final) { ... }

That's a bit laborious, just to find out if the property we're ultimately interested in, obj.foo.bar.final, exists or not.

JavaScript essentially allows you one level of non-existence when querying objects. Which is why we can interrogate obj.foo, even though it doesn't exist, but if we try to go to two levels, and the first doesn't exist, we'll get an error:

if (obj.foo.bar) { ... } //error - obj.foo is undefined

This is why we have to interrogate each level at a time. Using the AND (&&) operator means JavaScript quits any time one of the sub-conditions returns falsy, meaning any remaining sub-conditionals (to the right) are ignored, so we're protected from errors. In our example, obj.foo doesn't exist, so the other checks (for obj.foo.bar and obj.foo.bar.final) never run.

Anyway, so much for the back story. The optional chaining operator alleviates the need for all of this hoop-jumping. We can rewrite the above with:

if (obj.foo?.bar?.final) { ... }

Suddenly there's no need to worry about errors or to check each level step by step. The ? before each step tells JavaScript it's OK for the level not to exist, and not to error if so.

Use with dynamic properties 🔗

OK that's great for dot syntax, where we know the names of our properties. But what about with dynamic properties, using square-bracket syntax?

let finalStep = 'final'; if (obj.foo?.bar?[finalStep]) { ... }

That will error. Perhaps counter-intuitively, to use the optional chaining operator with square-bracket syntax we actually need the dot as well. So the corrected version would be:

if (obj.foo?.bar?.[finalStep]) { ... }

Weird having a dot before a square bracket, no? But the thing to remember is that, when using dot syntax, the dot doubles as both the generational separator and the dot belonging to the operator. With square-bracket syntax, it acts only as part of the operator. In other words, the operator is always ?., never ?, even when using square-bracket syntax.

Use with methods 🔗

If you're really lazy, you can even use the optional chaining operator to call methods which may or may not exist:

window.foo(); //will error - no such method window.foo?.(); //won't error!

Or, if the method lies at a deeper level:

window.foo?.bar?.();

I hope you found this helpful and enjoy using these operators in your code!

Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!