Table of contents:
- Falsy, Truthy and Nullish values
- Numeric separators
- Template literals
- Shorthand property names
- Logical operator AND
- Logical operator OR
- Nullish Coalescing operator
- Logical Assignment operator
- Optional Chaining
- Destructuring assignment
- Spread operator
- Rest operator
- Rest parameters
- Default function parameters
- Arrow functions expressions
- async/await
- try...catch...finally
Falsy, Truthy & Nullish values
JavaScript implicitly converts values when they are in a boolean context (such as in conditional operations)
Falsy values
Falsy values, when implicitly converted, are equivalent to false
:
false
''
0
,-0
,0n
,-0n
,0.0
,-0.0
null
undefined
NaN
Truthy values
Truthy values, when implicitly converted, are equivalent to true
:
true
- Any non-empty string
- Any number less than 0 ou greater than 0 (including floating-point numbers)
Infinity
,-Infinity
[]
(Array, empty or non-empty){}
(Object, empty or non-empty)
Nullish values
Nullish values are always falsy and there are two possible values:
null
undefined
Numeric separators
Numeric separators improve the readability of literal numbers by adding a visual separator between groups, using an underscore ( _ )
const oneMillion = 1_000_000; // 1000000
const oneBillion = 1_000_000_000; // 1000000000
const oneMillionFiftyCents = 1_000_000.5; // 1000000.50
Template literals
Template literals (or template strings) is a syntactic sugar to build strings.
Don't do this:
const fullname = "My name is " + username + " " + lastname;
Do this instead:
const fullname = `My name is ${username} ${lastname}`;
It's good practice to use template literals for concatenation and + for mathematical operations.
Shorthand property names
We can use a shorter syntax for property names in object literals to simplify object creation.
Don't do this:
const person = {
username: username,
lastname: lastname,
getFullname: getFullname,
};
Do this instead:
const person = {
username,
lastname,
getFullname,
};
Don't do this:
const person = {
person.name,
};
This way will throw an Uncaught SyntaxError: Unexpected token '.'
Do this instead:
const person = {
name: person.name,
};
Bonus:
const person = {
fullname: "Lucas Bittencourt",
getFullname() {
return this.fullname;
},
};
Learn more about here
Logical operator AND
The logical operator AND ( && ) returns the right-hand value if the left-hand value is truthy.
console.log(false && "Lucas Bittencourt"); // false
console.log(undefined && "Lucas Bittencourt"); // undefined
console.log(null && "Lucas Bittencourt"); // null
console.log(NaN && "Lucas Bittencourt"); // NaN
console.log(true && "Lucas Bittencourt"); // Lucas Bittencourt
console.log(1 && "Lucas Bittencourt"); // Lucas Bittencourt
console.log(1 && true && "Lucas Bittencourt"); // 'Lucas Bittencourt'
console.log(1 && false && "Lucas Bittencourt"); // false
JSX:
{
hasMinimumAge && <SomeComponent />;
}
Logical operator OR
The logical operator OR ( || ) returns the right-hand value if the left-hand value is falsy.
console.log(false || "Lucas Bittencourt"); // Lucas Bittencourt
console.log(undefined || "Lucas Bittencourt"); // Lucas Bittencourt
console.log(null || "Lucas Bittencourt"); // Lucas Bittencourt
console.log(NaN || "Lucas Bittencourt"); // Lucas Bittencourt
console.log(true || "Lucas Bittencourt"); // true
console.log(1 || "Lucas Bittencourt"); // 1
console.log(0 || false || "Lucas Bittencourt"); // Lucas Bittencourt
console.log(0 || true || "Lucas Bittencourt"); // true
console.log(1 || true || "Lucas Bittencourt"); // 1
Nullish Coalescing operator
The Nullish coalescing operator ( ?? ) returns the right-hand value if the left-hand value is nullish.
console.log("" ?? "Lucas Bittencourt"); // ''
console.log(0 ?? "Lucas Bittencourt"); // 0
console.log(true ?? "Lucas Bittencourt"); // true
console.log(null ?? "Lucas Bittencourt"); // "Lucas Bittencourt"
console.log(undefined ?? "Lucas Bittencourt"); // "Lucas Bittencourt"
console.log(undefined ?? null ?? "Lucas Bittencourt"); // 'Lucas Bittencourt'
console.log(undefined ?? false ?? "Lucas Bittencourt"); // false
Logical Assignment operator
We can now assign values conditionally using logical operators.
AND equals (&&=)
Only evaluates the right operand and assigns to the left if the left operand is truthy.
Don't do this:
let intersectionObserver = new IntersectionObserver();
function resetIntersectionObserver() {
if (intersectionObserver) {
intersectionObserver = new IntersectionObserver();
}
return intersectionObserver;
}
Do this instead:
let intersectionObserver = new IntersectionObserver();
function resetIntersectionObserver() {
intersectionObserver &&= new IntersectionObserver();
return intersectionObserver;
}
OR equals (||=)
Only evaluates the right operand and assigns to the left if the left operand is falsy.
Don't do this:
let intersectionObserver;
function getIntersectionObserver() {
if (!intersectionObserver) {
intersectionObserver = new IntersectionObserver();
}
return intersectionObserver;
}
Do this instead:
let intersectionObserver = null;
function getIntersectionObserver() {
intersectionObserver ||= new IntersectionObserver();
return intersectionObserver;
}
Nullish Coalescing equals (??=)
Only evaluates the right operand and assigns to the left if the left operand is nullish.
Don't do this:
let intersectionObserver = null;
function getIntersectionObserver() {
if (intersectionObserver === null || intersectionObserver === undefined) {
intersectionObserver = new IntersectionObserver();
}
return intersectionObserver;
}
Do this instead:
let intersectionObserver = null;
function getIntersectionObserver() {
intersectionObserver ??= new IntersectionObserver();
return intersectionObserver;
}
Optional Chaining
Optional Chaining is a safe way to access object properties that may be nullish.
Don't do this:
console.log(object.nullishReference.name);
This can lead to a runtime error: Uncaught TypeError: Cannot read properties of undefined (reading 'name')
Do this instead:
console.log(object.nullishReference?.name);
We also can use optional chaining in conditionals.
if (object.nullishReference?.name) {
console.log("Hello, world");
}
We also can use optional chaining in arrays.
console.log(userList?.[0]?.name);
We also can use optional chaining in functions.
getUserData?.();
getUserData?.()?.name;
Destructuring assignment
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
Don't do this:
const username = user.username;
const lastname = user.lastname;
Do this instead:
const { username, lastname } = user;
We can destructure from functions:
const { username, lastname } = getUser();
We can set a default value:
const { username = "Lucas", lastname } = user;
We can destructure nested objects:
const {
username,
lastname,
information: { email, age },
} = user;
We can rename object properties when destructuring:
const { username: name } = user;
We can create another object using the rest operator when destructuring:
const { username, lastname, ...userInfo } = user;
We can destructure function parameters:
function getUsername({ name, lastname }) {
return `${name} ${lastname}`;
}
Don't do this:
const user1 = userList[0];
const user2 = userList[1];
Do this instead:
const [user1, user2] = userList;
We can skip array elements when destructuring:
const [user1, user2, , user4] = userList;
We can destructure multidimensional arrays:
const [[a, , c], , [, h, i]] = myArray;
We can set a default value:
const [a = 1, b, c] = numberList;
We can destructure objects within arrays:
const [{ username, lastname }, user2] = userList;
We can destructure arrays as if they were objects:
const {
0: user1,
1: user2,
2: { username, lastname },
} = userList;
We can create another array using the rest operator when destructuring:
const [user1, user2, ...moreUsers] = userList;
We can destructure function parameters:
function sumNumbers([a, b, c]) {
return a + b + c;
}
Spread operator
Spread operator "expands" an array or object into its elements.
const newUser = { ...user, age: 25 };
const newUserList = [...userList, newUser];
We can spread nested object elements:
const newUser = {
information: {
...user.information,
username: "Lucas",
},
age: 25,
};
We can spread array elements as function arguments:
createUserList(argument1, argument2, ...arguments);
Rest operator
Rest operator collects multiple elements and "condenses" them into a single element.
const { username, lastname, ...otherInfo } = user;
const [a, b, c, ...otherNumbers] = numberList;
Rest parameters
Rest parameters represents variadic functions and provide a way to accept an indeterminate number of parameters.
function total(...numbers) {
return sum(numbers);
}
Default function parameters
We can define default function parameters for optional arguments.
function getUsername(username = "Lucas Bittencourt") {
return username;
}
We can set an object as default parameter:
function getUsername(user = { username: "Lucas Bittencourt" }) {
return user.username;
}
Arrow functions expressions
Arrow functions are compact functions.
const getUser = (username, lastname) => {
return {
username,
lastname,
};
};
When an arrow function has only one statement, we can omit the return
keyword and its brackets.
const getUsername = (username) => username.trim();
When returning a literal object, we need to ensure it is enclosed in parentheses:
const getUserInfo = (name) => ({
name: name.trim(),
});
We can also return a literal array:
const getNumbers = () => [1, 2, 3];
We can have an asynchronus arrow function:
const getUsername = async (username) => Promise.resolve(username);
async/await
JavaScript async/await is syntactic sugar for promises.
Don't do this:
function fetchUser() {
getUser().then((user) => console.log(user));
}
Do this instead:
async function fetchUser() {
const user = await getUser();
console.log(user);
}
top-level await
We can use a top-level await without the need for an asynchronus function.
const username = await Promise.resolve("Lucas Bittencourt");
try...catch...finally
The try...catch statement is comprised of a try block and either a catch block, a finally block, or both. The code in the try block is executed first, and if it throws an exception, the code in the catch block will be executed. The code in the finally block will always be executed before control flow exits the entire construct.
Don't do this:
function fetchUser() {
getUser()
.then((user) => console.log(user))
.catch((error) => console.error(error))
.finally(() => console.info("Código executado!"));
}
Do this instead:
async function fetchUser() {
try {
const user = await getUser();
console.info(user);
} catch (error) {
console.error(error);
} finally {
console.log("Código executado!");
}
}