Sumário:
- Valores falsy, truthy e nullish
- Separadores numéricos
- Template literals
- Nomes de propriedades abreviados
- Operador lógico AND
- Operador lógico OR
- Operador Nullish Coalescing
- Operadores de atribuição lógica
- Optional Chaining
- Atribuição via desestruturação
- Operador spread
- Operador rest
- Parâmetros rest
- Parâmetros padrão de funções
- Arrow functions
- async/await
- try...catch...finally
- Array.prototype.at
- findLast e findLastIndex
- Métodos imutáveis de array
- Object.groupBy e Map.groupBy
- Métodos de Set
- Iterator helpers
- structuredClone
- Object.hasOwn
- Campos privados de classe
Valores falsy, truthy e nullish
O JavaScript converte valores implicitamente quando eles estão em um contexto booleano (como em operações condicionais).
Valores falsy
Valores falsy, quando convertidos implicitamente, equivalem a false:
false""(string vazia, incluindo''e template literals vazios)0,-0,0nnullundefinedNaN
Valores truthy
Valores truthy, quando convertidos implicitamente, equivalem a true:
true- Qualquer string não vazia (incluindo
"0"e"false") - Qualquer número diferente de
0(incluindo números negativos e de ponto flutuante) Infinity,-Infinity[](Array, vazio ou não){}(Object, vazio ou não)
Valores nullish
Valores nullish são sempre falsy e existem dois valores possíveis:
nullundefined
Separadores numéricos
Os separadores numéricos melhoram a legibilidade de números literais adicionando um separador visual entre os grupos, usando um underscore ( _ )
const oneMillion = 1_000_000; // 1000000
const oneBillion = 1_000_000_000; // 1000000000
const oneMillionFiftyCents = 1_000_000.5; // 1000000.5
Template literals
Template literals (ou template strings) são um syntactic sugar para montar strings.
Não faça isso:
const fullname = "My name is " + username + " " + lastname;
Faça isso:
const fullname = `My name is ${username} ${lastname}`;
É uma boa prática usar template literals para concatenação e + para operações matemáticas.
Nomes de propriedades abreviados
Podemos usar uma sintaxe mais curta para os nomes de propriedades em object literals, simplificando a criação de objetos.
Não faça isso:
const person = {
username: username,
lastname: lastname,
getFullname: getFullname,
};
Faça isso:
const person = {
username,
lastname,
getFullname,
};
Não faça isso:
const person = {
user.name,
};
Desse jeito, vai ser lançado um Uncaught SyntaxError: Unexpected token '.'
Faça isso:
const person = {
name: user.name,
};
Bônus:
const person = {
fullname: "Lucas Bittencourt",
getFullname() {
return this.fullname;
},
};
Saiba mais sobre isso aqui
Operador lógico AND
O operador lógico AND ( && ) retorna o valor da direita se o valor da esquerda for truthy. Caso contrário, retorna o valor da esquerda.
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 />;
}
Cuidado com números no JSX: {items.length && <List />} renderiza 0 quando a lista está vazia.
Use {items.length > 0 && <List />} no lugar.
Operador lógico OR
O operador lógico OR ( || ) retorna o valor da direita se o valor da esquerda for falsy. Caso contrário, retorna o valor da esquerda.
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
Operador Nullish Coalescing
O operador Nullish Coalescing ( ?? ) retorna o valor da direita se o valor da esquerda for 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
Prefira ?? em vez de || para valores padrão quando 0, "" ou false forem valores válidos:
const timeout = options.timeout || 5_000; // 0 becomes 5000 (bug)
const timeout = options.timeout ?? 5_000; // 0 stays 0
Operadores de atribuição lógica
Podemos atribuir valores condicionalmente usando operadores lógicos.
Atribuição AND (&&=)
Só avalia o operando da direita e o atribui à esquerda se o operando da esquerda for truthy.
Não faça isso:
if (user.name) {
user.name = user.name.trim();
}
Faça isso:
user.name &&= user.name.trim();
Atribuição OR (||=)
Só avalia o operando da direita e o atribui à esquerda se o operando da esquerda for falsy.
Não faça isso:
if (!options.title) {
options.title = "Untitled";
}
Faça isso:
options.title ||= "Untitled";
Atribuição Nullish Coalescing (??=)
Só avalia o operando da direita e o atribui à esquerda se o operando da esquerda for nullish.
Não faça isso:
let cache = null;
function getCache() {
if (cache === null || cache === undefined) {
cache = new Map();
}
return cache;
}
Faça isso:
let cache = null;
function getCache() {
cache ??= new Map();
return cache;
}
A diferença entre ||= e ??=:
const options = { retries: 0 };
options.retries ||= 3; // 3, because 0 is falsy
options.retries ??= 3; // 0, because 0 is not nullish
Optional Chaining
Optional Chaining é uma forma segura de acessar propriedades de objetos que podem ser nullish.
Não faça isso:
console.log(object.nullishReference.name);
Isso pode causar um erro em tempo de execução: Uncaught TypeError: Cannot read properties of undefined (reading 'name')
Faça isso:
console.log(object.nullishReference?.name);
Também podemos usar optional chaining em condicionais.
if (object.nullishReference?.name) {
console.log("Hello, world");
}
Também podemos usar optional chaining em arrays.
console.log(userList?.[0]?.name);
Também podemos usar optional chaining em funções.
getUserData?.();
getUserData?.()?.name;
Podemos combiná-lo com o operador Nullish Coalescing para fornecer um valor padrão.
const city = user.address?.city ?? "Unknown";
Atribuição via desestruturação
A sintaxe de atribuição via desestruturação é uma expressão JavaScript que possibilita extrair valores de arrays, ou propriedades de objetos, para variáveis distintas.
Não faça isso:
const username = user.username;
const lastname = user.lastname;
Faça isso:
const { username, lastname } = user;
Podemos desestruturar o retorno de funções:
const { username, lastname } = getUser();
Podemos definir um valor padrão:
const { username = "Lucas", lastname } = user;
Podemos desestruturar objetos aninhados:
const {
username,
lastname,
information: { email, age },
} = user;
Podemos renomear propriedades do objeto ao desestruturar:
const { username: name } = user;
Podemos criar outro objeto usando o operador rest ao desestruturar:
const { username, lastname, ...userInfo } = user;
Podemos desestruturar parâmetros de funções:
function getUsername({ name, lastname }) {
return `${name} ${lastname}`;
}
Não faça isso:
const user1 = userList[0];
const user2 = userList[1];
Faça isso:
const [user1, user2] = userList;
Podemos pular elementos do array ao desestruturar:
const [user1, user2, , user4] = userList;
Podemos desestruturar arrays multidimensionais:
const [[a, , c], , [, h, i]] = myArray;
Podemos definir um valor padrão:
const [a = 1, b, c] = numberList;
Podemos desestruturar objetos dentro de arrays:
const [{ username, lastname }, user2] = userList;
Podemos desestruturar arrays como se fossem objetos:
const {
0: user1,
1: user2,
2: { username, lastname },
} = userList;
Podemos criar outro array usando o operador rest ao desestruturar:
const [user1, user2, ...moreUsers] = userList;
Podemos desestruturar parâmetros de funções:
function sumNumbers([a, b, c]) {
return a + b + c;
}
Podemos trocar o valor de variáveis sem uma variável temporária:
[a, b] = [b, a];
Operador spread
O operador spread "expande" um array ou objeto em seus elementos.
const newUser = { ...user, age: 25 };
const newUserList = [...userList, newUser];
Podemos espalhar elementos de objetos aninhados:
const newUser = {
...user,
information: {
...user.information,
username: "Lucas",
},
age: 25,
};
Podemos espalhar elementos de um array como argumentos de uma função:
const numbers = [5, 1, 8];
Math.max(...numbers); // 8
O spread cria apenas uma cópia rasa (shallow copy): objetos aninhados continuam compartilhados com o objeto original. Para criar uma cópia profunda (deep copy), use structuredClone.
Operador rest
O operador rest coleta múltiplos elementos e os "condensa" em um único elemento.
const { username, lastname, ...otherInfo } = user;
const [a, b, c, ...otherNumbers] = numberList;
Parâmetros rest
Os parâmetros rest oferecem uma forma de aceitar um número indefinido de argumentos como um array (funções variádicas).
function sum(...numbers) {
return numbers.reduce((total, number) => total + number, 0);
}
sum(1, 2, 3); // 6
Prefira parâmetros rest em vez do antigo objeto arguments, que não é um array de verdade e não existe em arrow functions.
Parâmetros padrão de funções
Podemos definir parâmetros padrão de funções para argumentos opcionais.
function getUsername(username = "Lucas Bittencourt") {
return username;
}
Podemos definir um objeto como parâmetro padrão:
function getUsername(user = { username: "Lucas Bittencourt" }) {
return user.username;
}
Podemos combinar isso com desestruturação para criar opções nomeadas:
function createUser({ name, role = "user", active = true } = {}) {
return { name, role, active };
}
createUser({ name: "Lucas" }); // { name: "Lucas", role: "user", active: true }
Arrow functions
Arrow functions são funções compactas.
const getUser = (username, lastname) => {
return {
username,
lastname,
};
};
Quando uma arrow function tem apenas uma expressão, podemos omitir a palavra-chave return e as chaves.
const getUsername = (username) => username.trim();
Ao retornar um objeto literal, precisamos garantir que ele esteja envolvido em parênteses:
const getUserInfo = (name) => ({
name: name.trim(),
});
Também podemos retornar um array literal:
const getNumbers = () => [1, 2, 3];
Podemos ter uma arrow function assíncrona:
const getUser = async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};
Arrow functions não têm o seu próprio this: elas usam o this do lugar onde foram criadas.
É por isso que são ótimas para callbacks, mas não devem ser usadas como métodos de objetos.
async/await
O async/await do JavaScript é um syntactic sugar para promises.
Não faça isso:
function fetchUser() {
getUser().then((user) => console.log(user));
}
Faça isso:
async function fetchUser() {
const user = await getUser();
console.log(user);
}
top-level await
Em ES modules (por exemplo, arquivos .mjs, "type": "module" ou <script type="module">), podemos usar await
no nível mais alto do módulo, sem uma função async.
const config = await fetch("/config.json").then((response) => response.json());
Executando promises em paralelo
Aguardar promises independentes uma por uma faz com que elas rodem em sequência.
Não faça isso:
// the second request only starts after the first one finishes
const user = await getUser();
const posts = await getPosts();
Faça isso:
// both requests run at the same time
const [user, posts] = await Promise.all([getUser(), getPosts()]);
Promise.all: rejeita assim que uma promise rejeitaPromise.allSettled: espera todas elas e retorna o status de cada umaPromise.race: é resolvida (settled) com a primeira promise que for resolvida (settled)Promise.any: é cumprida (fulfilled) com a primeira promise que for cumprida (fulfilled)
const results = await Promise.allSettled([getUser(), getPosts()]);
for (const result of results) {
if (result.status === "fulfilled") {
console.log(result.value);
} else {
console.error(result.reason);
}
}
Promise.withResolvers
Cria uma promise e a retorna junto com as suas funções resolve e reject,
útil para transformar APIs baseadas em callback em promises.
Não faça isso:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
Faça isso:
const { promise, resolve, reject } = Promise.withResolvers();
socket.once("ready", resolve);
socket.once("error", reject);
await promise;
try...catch...finally
A instrução try...catch é composta por um bloco try e um bloco catch, um bloco finally, ou ambos. O código no bloco try é executado primeiro e, se ele lançar uma exceção, o código no bloco catch será executado. O código no bloco finally sempre será executado antes de o fluxo de controle sair de toda a estrutura.
Não faça isso:
function fetchUser() {
getUser()
.then((user) => console.log(user))
.catch((error) => console.error(error))
.finally(() => console.info("Finished!"));
}
Faça isso:
async function fetchUser() {
try {
const user = await getUser();
console.info(user);
} catch (error) {
console.error(error);
} finally {
console.log("Finished!");
}
}
Se você não precisa do erro, pode omiti-lo:
function parseJson(text) {
try {
return JSON.parse(text);
} catch {
return null;
}
}
Error cause
Quando você captura um erro e lança um novo, use a opção cause para manter o erro original.
async function getUser(id) {
try {
return await database.users.find(id);
} catch (error) {
throw new Error(`Failed to get user ${id}`, { cause: error });
}
}
Array.prototype.at
at() retorna o item no índice informado e aceita índices negativos para contar a partir do final.
Não faça isso:
const lastUser = userList[userList.length - 1];
Faça isso:
const lastUser = userList.at(-1);
findLast e findLastIndex
Como find e findIndex, mas buscando a partir do final do array.
const numbers = [1, 2, 3, 4, 5];
numbers.findLast((number) => number % 2 === 0); // 4
numbers.findLastIndex((number) => number % 2 === 0); // 3
Métodos imutáveis de array
sort, reverse e splice mutam o array original, o que causa bugs, por exemplo, com o state do React.
As versões imutáveis deles retornam um novo array e mantêm o original intacto:
toSorted()em vez desort()toReversed()em vez dereverse()toSpliced()em vez desplice()with(index, value)em vez dearray[index] = value
Não faça isso:
const sortedUsers = [...users].sort((a, b) => a.name.localeCompare(b.name));
Faça isso:
const sortedUsers = users.toSorted((a, b) => a.name.localeCompare(b.name));
const numbers = [3, 1, 2];
numbers.toSorted(); // [1, 2, 3]
numbers.toReversed(); // [2, 1, 3]
numbers.toSpliced(0, 1); // [1, 2]
numbers.with(0, 10); // [10, 1, 2]
console.log(numbers); // [3, 1, 2], unchanged
Object.groupBy e Map.groupBy
Agrupa os itens de um array (ou de qualquer iterável) pelo valor retornado por um callback.
Não faça isso:
const usersByRole = users.reduce((groups, user) => {
groups[user.role] ??= [];
groups[user.role].push(user);
return groups;
}, {});
Faça isso:
const usersByRole = Object.groupBy(users, (user) => user.role);
// { admin: [...], user: [...] }
Use Map.groupBy quando as chaves não forem strings, como objetos ou datas:
const usersByTeam = Map.groupBy(users, (user) => user.team);
usersByTeam.get(team); // users of that team
Métodos de Set
Os Sets agora têm métodos para combiná-los e compará-los:
const frontend = new Set(["Lucas", "Ana", "John"]);
const backend = new Set(["Lucas", "Mary"]);
frontend.union(backend); // Set { "Lucas", "Ana", "John", "Mary" }
frontend.intersection(backend); // Set { "Lucas" }
frontend.difference(backend); // Set { "Ana", "John" }
frontend.symmetricDifference(backend); // Set { "Ana", "John", "Mary" }
new Set(["Lucas"]).isSubsetOf(frontend); // true
frontend.isSupersetOf(new Set(["Ana"])); // true
frontend.isDisjointFrom(new Set(["Peter"])); // true
Lembre-se: set.has() é muito mais rápido do que array.includes() em listas grandes.
Iterator helpers
Os iterators agora têm métodos como map, filter, take, drop, flatMap, reduce, some, every, find e toArray.
Diferente dos métodos de array, eles são lazy: cada item passa pela cadeia inteira, um de cada vez,
sem criar arrays intermediários, e o processamento para assim que possível.
function* naturalNumbers() {
let number = 1;
while (true) {
yield number++;
}
}
const firstEvenSquares = naturalNumbers()
.filter((number) => number % 2 === 0)
.map((number) => number ** 2)
.take(3)
.toArray();
// [4, 16, 36], even with an infinite generator
Eles também funcionam com os iterators de Map e Set:
const adminNames = usersById
.values()
.filter((user) => user.role === "admin")
.map((user) => user.name)
.toArray();
structuredClone
structuredClone cria uma cópia profunda (deep copy) de um valor. Está disponível nos navegadores, Node.js, Deno e Bun.
Não faça isso:
const copy = JSON.parse(JSON.stringify(user));
Isso perde objetos Date (eles viram strings), Map, Set, valores undefined e falha com referências circulares.
Faça isso:
const copy = structuredClone(user);
Funções e nós do DOM não podem ser clonados (é lançado um DataCloneError), e instâncias de classes perdem o seu prototype (viram objetos simples).
Object.hasOwn
Verifica se um objeto tem uma propriedade própria (não herdada).
Não faça isso:
Object.prototype.hasOwnProperty.call(user, "name");
Faça isso:
Object.hasOwn(user, "name");
Campos privados de classe
Campos e métodos de classe que começam com # são realmente privados: não podem ser acessados fora da classe.
class Counter {
#count = 0;
increment() {
this.#count++;
return this.#count;
}
static create() {
return new Counter();
}
}
const counter = Counter.create();
counter.increment(); // 1
counter.#count; // SyntaxError: Private field '#count' must be declared in an enclosing class
Diferente da palavra-chave private do TypeScript, que só existe em tempo de compilação, os campos # são privados em tempo de execução.