Nullish operators
To understand nullish operators, we need to define what "nullish" means first: A variable's value is considered nullish if it is null
or undefined
. Code will occasionally have to check if a value is nullish, to set default values for code further down that expects genuine values like objects or strings.
As a quick way to return either a value or a fallback if it is nullish, use the nullish coalescing operator ??
:
return x ?? "default"
Returns the value of x
if it isn't nullish, else it returns the string "default"
instead.
If you want to assign a default value to a variable, but only if it is currently nullish, you can use the nullish coalescing assignment ??=
:
let x ??= 5
This will set the variable x
to 5
if it is currently null
or undefined
.
Optional chaining
Chaining property and function accesses together is a common way to shorten javascript code. But what if you suspect one or more elements to null
/ undefined
? When trying to access child properties or functions of nullish elements, a TypeError
exception will be thrown. If you want to simply access a child element or do nothing if it is nullish, you can use the optional chaining operator ?.
:
let car = {}
let carModel = car.model?.name
In this example, the car
variable has no property model
, so car.model
is undefined
. Trying to access undefined.name
will throw a TypeError
exception. The optional chaining operator circumvents this by short-circuiting the code the moment a nullish child property is accessed and returns undefined
instead
One important thing to note is that the operator is placed after the property you suspect of being nullish. This leads to a not immediately obvious syntax for functions:
let car = {}
let carModel = car.drive?.()
Here the optional chaining operator is placed between the name of the nonexistent function and the parenthesis () used to execute it. This is because accessing car.drive
itself is legal and returns undefined
, but trying to execute undefined()
will throw the exception, thus the operator is placed between them.
Ternary operator
The ternary operator is often used as a single-line shorthand for an if
/else
condition. It is used by providing a condition, followed by a ?
, the code to run if the condition is true
, then a :
and the code to run if the condition is false
:
2 > 1 ? console.log("True") : console.log("False")
Here, 2 > 1
is the condition. The condition returns true
, so the first console.log()
call is executed. If it were false, the second one would be called instead.
Spread Syntax
The spread syntax ...
has different behaviours depending on context. In a function's parameter list, it will catch all parameters and return them as an array:
function greet(...guests){
console.log("Hello ", guests)
}
This allows for the definition of variadic functions that take any number of parameters, or alternatively define some and catch the rest in a single variable.
The second use is to expand an iterable's values into single values, for example:
function sum(a, b){ return a + b } console.log(sum(...[4, 5]))
This passes the values of the array [4, 5]
as individual parameters to the sum
function.
Lastly, the spread syntax can be used to clone an object's properties into a new one:
let x = {"one": 1, "two": 2}
let y = {...x, "three": 3}
let z = {...x, ...y}
The value of y
is now {"one": 1, "two": 2, "three": 3}
, because the first two properties were copied from x
. The contents of z
are equal to y
, because it contains the merged contents of x
and y
(and y
already contained everything in x
).
Destructuring
Destructuring assignments allow the extraction of object properties/array items into variables. While destructuring or arrays uses square brackets []
, destructuring object properties requires curly brackets {}
:
let [a, b] = [1, 2]
let {a, b} = {"a": 1, "b": 2}
let {b, a} = {"a": 1, "b": 2}
While the array simply assigns values based on sequence (first variable gets the first value of the array etc), objects use property names instead, so the order of the variables doesn't matter. This makes the last 2 lines equal. The let
declaration applies to both a
and b
. If you change it to for example const
, it will declare all destructured variables as const
. If you need some to be read-only and others to be reassignable, you'll have to destructure twice.
For arrays, you can also skip values during assignment:
let [a, , b] = [1, 2, 3]
This skips the second value, setting a
to 1
and b
to 3
respectively.
For objects, you can use nested destructuring:
let {a, b:{c}} = { a: 1, b: { c: 2 } };
This uses nested destructuring to assign the value of b.c
to the destructured variable b
.
Lastly, destructuring supports the spreading syntax:
let {a, ...b} = [1, 2, 3]
Using the spread syntax allows us to catch all remaining values in a single variable. In this case, a
is set to 1
and b
contains the array [2, 3]
.