ECMAScript ES7 ao ES10: as features que você usa sem saber de onde vieram
Novidades do JavaScript ES7 ao ES10: includes, spread em objetos, flat, fromEntries e gotchas reais. Guia prático com exemplos.
Jonathan Juliani
Engenheiro de Software Sr - Full Stack · WithClutch Inc. | Juliani Tecnologia
JavaScript
Você provavelmente já usou includes(), spread em objetos e flat() no dia a dia — mas sabia que essas três features vieram em releases diferentes do ECMAScript? Bora ver o que mudou do ES7 ao ES10, com os casos que a documentação oficial passa batido.
O que você vai aprender:
Array.includes() e String.includes() — e o gotcha com objetos que pega todo mundo
Object.values() e Object.entries() — e o irmão menor que virou maior no ES10
Spread operator em objetos — por que substituiu o Object.assign de vez
finally em Promises e for await...of — onde cada um encaixa de verdade
flat(), flatMap(), Object.fromEntries(), trimStart/End e o catch sem parâmetro
Por que o sort() virar stable no ES10 importa mais do que parece
Pré-requisitos: familiaridade com JavaScript básico (funções, arrays, objetos). Se você nunca viu uma Promise, vale dar uma passada nesse post antes de chegar no ES9.
Inscreva-se no Ledger da Engenharia
Receba notas de arquitetura e performance na sua caixa de entrada. Mesma lista do convite—inscreva-se aqui quando quiser.
O ES2016 (ES7) foi um release enxuto — três adições. Mas uma delas você usa provavelmente toda semana.
Array.includes e String.includes: chega de indexOf
Antes do ES7, checar se um valor existia num array ou string significava usar indexOf() e torcer pra lembrar que ele retorna -1 quando não encontra — não false. O includes() resolve isso com um retorno direto em boolean.
Em strings:
código
const frase = 'Hello world';frase.includes('w'); // truefrase.includes('L'); // false — é case-sensitive, se ligafrase.includes('x'); // false
Em arrays simples, funciona exatamente como você espera:
"Tá Jon, mas eu passei exatamente o mesmo objeto. Por que retorna false?"
Porque o includes() faz comparação por referência, não por valor. { id: 1, desc: 'Obj 1' } que você passou no includes é um objeto novo na memória — diferente do que está no array, mesmo tendo os mesmos valores. Pra comparar objetos dentro de array você precisa de um find() ou some() com comparação explícita das propriedades.
Operador exponencial **
Antes você precisava do Math.pow(5, 2) pra elevar à potência. Agora:
código
const elevarA2 = (num) => num ** 2;elevarA2(5); // 25const elevarA3 = (num) => num ** 3;elevarA3(5); // 125
Sussa.
ES8: padStart, padEnd, Object.values e Object.entries
O ES2017 trouxe algumas features que parecem pequenas mas aparecem bastante em código de produção.
padStart e padEnd: formatação de string sem gambiarra
O padStart e padEnd adicionam caracteres (espaço por padrão, ou o que você passar) no início ou no final da string até ela atingir o tamanho especificado. Útil pra formatar datas, IDs, logs:
código
const nome = 'Jonathan';nome.padStart(10); // ' Jonathan' — 2 espaços no começonome.padEnd(10); // 'Jonathan ' — 2 espaços no final// caso de uso real: formatar número de pedido com zeros à esquerdaconst pedido = '42';pedido.padStart(6, '0'); // '000042'
Com entries() você consegue iterar sobre objetos com map, reduce, for...of — coisa que não rola diretamente num objeto simples. Um caso que aparece bastante: transformar as chaves ou valores de um objeto sem perder a estrutura. Vai ficar ainda mais útil quando chegar no Object.fromEntries lá no ES10.
[IMAGEM: diagrama comparando a saída de Object.values vs Object.entries no mesmo objeto | alt: "Diagrama mostrando Object.values retornando array de valores e Object.entries retornando array de pares chave-valor"]
ES9: spread em objetos, finally e for await
O ES2018 foi um dos releases mais úteis pra quem trabalha com async e com objetos no dia a dia.
Spread em objetos: chega de Object.assign
O spread (...) já funcionava com arrays desde o ES6. Com objetos só chegou no ES9 — e substitui o Object.assign na maioria dos casos com uma sintaxe muito mais limpa.
"Tá Jon, mas o Object.assign já fazia cópia. Quando uso um em vez do outro?"
O spread cria uma cópia rasa (shallow) e é mais legível. O Object.assign ainda tem uma vantagem: você pode passar múltiplos objetos fonte de uma vez e já vai mergindo tudo no destino — Object.assign(destino, obj1, obj2, obj3). Com spread você faz { ...obj1, ...obj2, ...obj3 }, que é igualmente válido. Na prática, spread virou o padrão — Object.assign aparece mais em código antigo ou quando você precisa mutar o objeto de destino intencionalmente.
Se você veio de Java, PHP ou Python já conhece o finally do try/catch. No JS demorou, mas chegou no ES9 — e agora funciona tanto no try/catch quanto encadeado em Promises:
código
const url = 'https://jsonplaceholder.typicode.com/users';const fetchUsers = async () => { const data = await fetch(url) .then((res) => res.json()) .catch((err) => console.log('Deu ruim:', err)) .finally(() => console.log('Requisição finalizada — com ou sem erro'));};fetchUsers();
O finally executa independente de o then ou o catch ter rodado. Útil pra fechar loaders, limpar estado de loading, fechar conexões — qualquer coisa que tem que acontecer sempre.
for await...of: iterando Promises em sequência
Esse é o mais tretinha do ES9. Quando você tem um array de Promises e precisa iterar o resultado de cada uma em sequência — não em paralelo — o for await...of é a solução limpa.
código
const urls = [ 'https://jsonplaceholder.typicode.com/users', 'https://jsonplaceholder.typicode.com/posts', 'https://jsonplaceholder.typicode.com/albums',];const fetchData = async (urls) => { const fetchArray = urls.map((url) => fetch(url)); // dispara todos de uma vez for await (const result of fetchArray) { // aguarda cada um em ordem const data = await result.json(); console.log(data); }};fetchData(urls);
"Tá Jon, mas quando uso isso em vez de Promise.all?"
O Promise.all executa em paralelo e aguarda todos terminarem — se um falhar, todos falham juntos. O for await processa cada Promise em sequência, um por vez, e você pode tratar o erro individualmente dentro do loop. Usa Promise.all quando a ordem não importa e você quer velocidade máxima. Usa for await quando precisa de controle granular — como processar um upload por vez pra não sobrecarregar o servidor.
ES10: flat, fromEntries, trim melhorado e sort estável
O ES2019 trouxe um conjunto de features que parecem pequenas isoladas, mas que mudam bastante o jeito de trabalhar com arrays e objetos.
flat e flatMap: adeus array aninhado
O flat() pega sub-arrays e "joga" os valores pro array pai. Por padrão, ele desaninha só um nível:
Sem o flatMap, você faria numeros.map(...).flat() — funciona igual, mas é um passo a mais.
Object.fromEntries: o par do entries
Lembra do Object.entries() lá no ES8 que transforma objeto em array de pares [chave, valor]? O Object.fromEntries() faz exatamente o inverso — transforma um array de pares em objeto:
O caso de uso que aparece bastante na prática: você tem um objeto, quer transformar só os valores (sem perder as chaves), e depois voltar pra objeto. Com entries + map + fromEntries isso vira uma linha:
Antes do ES10 você faria isso com reduce — funciona, mas o fromEntries deixa a intenção muito mais explícita.
trimStart e trimEnd: nomes que fazem sentido
Já existiam trimLeft e trimRight — mas os nomes eram inconsistentes com o resto da API do JS. O ES10 trouxe os aliases trimStart e trimEnd, que fazem a mesma coisa com nomes mais coerentes:
código
const comEspacos = ' Hello world ';comEspacos.trimStart(); // 'Hello world 'comEspacos.trimEnd(); // ' Hello world'comEspacos.trim(); // 'Hello world' — remove dos dois lados, esse é o ES5
catch sem parâmetro: pequeno, mas útil
Antes do ES10, o catch exigia que você declarasse o parâmetro do erro — mesmo quando não ia usar:
código
// antestry { // algo} catch (err) { // err nunca usado, mas tinha que estar aqui console.log('deu ruim');}// ES10 em diantetry { // algo} catch { console.log('deu ruim');}
sort() stable: o que mudou e por que importa
Esse é o mais sutil, mas tem consequência real.
Um algoritmo de ordenação é stable quando elementos com o mesmo valor de comparação mantêm a ordem original entre si. Unstable significa que eles podem trocar de posição mesmo tendo o mesmo peso na ordenação.
[IMAGEM: diagrama comparando sort stable vs unstable com um array de objetos com mesmo valor de prioridade | alt: "Diagrama mostrando sort stable mantendo a ordem original de elementos com mesmo valor, e sort unstable trocando suas posições"]
Na prática: imagine que você tem uma lista de tarefas com prioridade, ordenada por data. Ao aplicar sort() por prioridade, um algoritmo stable mantém a ordem por data dentro das tarefas de mesma prioridade. Um unstable pode embaralhar.
Antes do ES10, o comportamento do sort() dependia do browser e do tamanho do array — Chrome usava um algoritmo diferente do Firefox, e arrays acima de certo tamanho podiam ter comportamento imprevisível. A partir do ES10, todo ambiente JavaScript é obrigado a implementar um sort stable. Se você já teve bug esquisito de ordenação que só aparecia em produção ou em certos browsers, esse pode ter sido o culpado.
TL;DR — Referência rápida ES7 ao ES10
Feature
Versão
O que faz
Gotcha
Array.includes()
ES7
Verifica se valor existe — retorna boolean
Comparação por referência: não funciona com objetos
String.includes()
ES7
Verifica se substring existe
Case-sensitive
** (exponencial)
ES7
Eleva à potência
—
padStart / padEnd
ES8
Preenche string até tamanho alvo
—
Object.values()
ES8
Array com os valores do objeto
—
Object.entries()
ES8
Array de pares [chave, valor]
—
Spread em objetos ...
ES9
Copia / desestrutura objetos
Cópia rasa — objetos aninhados ainda compartilham referência
Promise.finally()
ES9
Executa sempre, com ou sem erro
—
for await...of
ES9
Itera Promises em sequência
Diferente de Promise.all — sequencial, não paralelo
flat()
ES10
Desaninha sub-arrays
Por padrão só 1 nível — use flat(Infinity) pra tudo
flatMap()
ES10
map() + flat(1) combinados
Só desaninha 1 nível, diferente de flat(Infinity)
Object.fromEntries()
ES10
Array de pares → objeto
Inverso do Object.entries()
trimStart / trimEnd
ES10
Remove espaços no início / fim
Aliases de trimLeft / trimRight
catch {} sem parâmetro
ES10
Omite o parâmetro err quando não vai usar
—
sort() stable
ES10
Mantém ordem relativa de itens iguais
Comportamento era inconsistente entre browsers antes disso
Próximos passos
Se esse artigo fez você querer entender melhor o fundamento de algumas dessas features:
CommonJS no Node: require e module.exports ainda convivem com import — base útil antes de misturar módulos: Node require e exports explicados
Spread e rest operators em profundidade — arrays, objetos, parâmetros de função e os gotchas de cópia rasa: lê aqui
Promises e async/await do zero, pra chegar no for await e no finally sem travar: começa por aqui
Documentação oficial do TC39 com o histórico completo de cada proposta: tc39.es/ecma262
Antes de fechar
Qual dessas features você mais usa sem saber que era "nova"? Tenho uma suspeita que o spread em objetos é o campeão.
Se viu alguma cagada, se discorda de alguma coisa ou tem um caso de uso melhor pra alguma dessas features — me fala nos comentários. Vamos compartilhar conhecimento e código, é o melhor jeito de fixar qualquer coisa.