O JavaScript evoluiu drasticamente desde o ES6 (2015), adicionando funcionalidades que tornam o código mais legível, fácil de manter e poderoso. Compreender o JavaScript moderno — desde destructuring até async/await — é essencial para escrever código profissional. Este guia aborda as principais funcionalidades do JavaScript moderno na perspetiva de um programador sénior.
Porque é que o JavaScript Moderno é Importante
O JavaScript moderno permite:
- Código mais limpo: Menos boilerplate, mais expressivo
- Menos bugs: Funcionalidades que evitam erros comuns
- Melhor desempenho: Funcionalidades da linguagem otimizadas
- Gestão de async: Programação assíncrona elegante
- Padrões funcionais: Suporte de primeira classe para funções
Declarações de Variáveis
let e const
// const: Não pode ser reatribuído (usar por defeito)
const API_URL = 'https://api.example.com';
const config = { timeout: 5000 };
// Pode modificar propriedades de objetos const
config.timeout = 10000; // OK
// config = {}; // Erro: Não é possível reatribuir
// let: Pode ser reatribuído (usar quando necessário)
let count = 0;
count = 1; // OK
// Escopo de bloco
if (true) {
const scoped = 'only here';
let alsoScoped = 'only here too';
}
// scoped e alsoScoped não estão acessíveis aqui
// Problemas de hoisting com var (evitar var)
console.log(x); // undefined (hoisted)
var x = 5;
Destructuring
Destructuring de Objetos
const user = {
name: 'John',
email: '[email protected]',
address: {
city: 'NYC',
zip: '10001'
}
};
// Destructuring básico
const { name, email } = user;
console.log(name); // «John»
// Renomear variáveis
const { name: userName, email: userEmail } = user;
console.log(userName); // «John»
// Valores por defeito
const { role = 'user' } = user;
console.log(role); // «user» (não existe em user)
// Destructuring aninhado
const { address: { city, zip } } = user;
console.log(city); // «NYC»
// Operador rest
const { name: n, ...rest } = user;
console.log(rest); // { email: '...', address: {...} }
// Parâmetros de função
function greet({ name, email }) {
console.log(`Hello ${name} (${email})`);
}
greet(user);
// Com valores por defeito nos parâmetros
function createUser({ name = 'Anonymous', role = 'user' } = {}) {
return { name, role };
}
createUser(); // { name: 'Anonymous', role: 'user' }
Destructuring de Arrays
const colors = ['red', 'green', 'blue', 'yellow'];
// Destructuring básico de arrays
const [first, second] = colors;
console.log(first); // «red»
// Saltar elementos
const [, , third] = colors;
console.log(third); // «blue»
// Operador rest
const [primary, ...others] = colors;
console.log(others); // ['green', 'blue', 'yellow']
// Valores por defeito
const [a, b, c, d, e = 'purple'] = colors;
console.log(e); // «purple»
// Trocar variáveis
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2, 1
// Retorno de função
function getCoordinates() {
return [40.7128, -74.0060];
}
const [lat, lng] = getCoordinates();
Operador Spread
Arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// Combinar arrays
const combined = [...arr1, ...arr2];
// [1, 2, 3, 4, 5, 6]
// Copiar array (superficial)
const copy = [...arr1];
// Adicionar elementos
const withExtra = [0, ...arr1, 4];
// [0, 1, 2, 3, 4]
// Converter iterável em array
const chars = [...'hello'];
// ['h', 'e', 'l', 'l', 'o']
// Argumentos de função
function sum(a, b, c) {
return a + b + c;
}
sum(...arr1); // 6
Objetos
const defaults = { theme: 'light', lang: 'en' };
const userPrefs = { theme: 'dark' };
// Fundir objetos (as propriedades posteriores prevalecem)
const settings = { ...defaults, ...userPrefs };
// { theme: 'dark', lang: 'en' }
// Copiar objeto (superficial)
const copy = { ...defaults };
// Adicionar/substituir propriedades
const extended = { ...defaults, fontSize: 16 };
// Spread condicional
const extra = true;
const obj = {
required: 'value',
...(extra && { optional: 'included' })
};
Template Literals
const name = 'John';
const age = 30;
// Interpolação de strings
const greeting = `Hello, ${name}! You are ${age} years old.`;
// Expressões em templates
const message = `Next year you'll be ${age + 1}`;
// Strings multi-linha
const html = `
<div class="card">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`;
// Templates tagged
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] ? `<strong>${values[i]}</strong>` : '';
return result + str + value;
}, '');
}
const highlighted = highlight`Hello ${name}, you are ${age}!`;
// 'Hello <strong>John</strong>, you are <strong>30</strong>!'
Arrow Functions
// Sintaxe básica
const add = (a, b) => a + b;
// Parâmetro único (não precisa de parênteses)
const double = x => x * 2;
// Sem parâmetros
const getRandom = () => Math.random();
// Multi-linha (precisa de chavetas e return)
const calculate = (a, b) => {
const sum = a + b;
const product = a * b;
return { sum, product };
};
// Devolver literal de objeto (envolver em parênteses)
const createUser = (name, email) => ({ name, email });
// Métodos de array
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
// this lexical (sem binding próprio de this)
const obj = {
name: 'Object',
greet() {
// Arrow function herda this de greet()
setTimeout(() => {
console.log(this.name); // «Object»
}, 100);
}
};
Métodos de Array
const users = [
{ id: 1, name: 'John', age: 30 },
{ id: 2, name: 'Jane', age: 25 },
{ id: 3, name: 'Bob', age: 35 }
];
// map: Transformar cada elemento
const names = users.map(u => u.name);
// ['John', 'Jane', 'Bob']
// filter: Manter elementos correspondentes
const adults = users.filter(u => u.age >= 30);
// [{ id: 1, ... }, { id: 3, ... }]
// find: Obter o primeiro elemento correspondente
const john = users.find(u => u.name === 'John');
// { id: 1, name: 'John', age: 30 }
// findIndex: Obter o índice da primeira correspondência
const johnIndex = users.findIndex(u => u.name === 'John');
// 0
// some: Verificar se existe alguma correspondência
const hasAdult = users.some(u => u.age >= 18);
// true
// every: Verificar se todas correspondem
const allAdults = users.every(u => u.age >= 18);
// true
// reduce: Acumular para um único valor
const totalAge = users.reduce((sum, u) => sum + u.age, 0);
// 90
// Encadeamento
const result = users
.filter(u => u.age >= 30)
.map(u => u.name)
.sort();
// ['Bob', 'John']
// includes
const hasJohn = names.includes('John'); // true
// flat: Achatar arrays aninhados
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.flat(); // [1, 2, 3, 4, 5]
// flatMap: map + flat
const sentences = ['Hello World', 'Goodbye World'];
const words = sentences.flatMap(s => s.split(' '));
// ['Hello', 'World', 'Goodbye', 'World']
Object Shorthand
const name = 'John';
const age = 30;
// Abreviatura de propriedades
const user = { name, age };
// O mesmo que: { name: name, age: age }
// Abreviatura de métodos
const obj = {
greet() {
return 'Hello';
},
// O mesmo que: greet: function() { return 'Hello'; }
};
// Nomes de propriedades computados
const key = 'dynamicKey';
const dynamic = {
[key]: 'value',
[`${key}_backup`]: 'backup'
};
// { dynamicKey: 'value', dynamicKey_backup: 'backup' }
Optional Chaining e Nullish Coalescing
const user = {
name: 'John',
address: {
city: 'NYC'
}
};
// Optional chaining (?.)
const zip = user?.address?.zip; // undefined (sem erro)
const country = user?.address?.country?.code; // undefined
// Sem optional chaining (forma antiga)
const zipOld = user && user.address && user.address.zip;
// Com arrays
const first = users?.[0]?.name;
// Com funções
const result = obj?.method?.();
// Nullish coalescing (??)
// Devolve o lado direito apenas se o lado esquerdo for null ou undefined
const value = null ?? 'default'; // «default»
const zero = 0 ?? 'default'; // 0 (0 não é nullish)
const empty = '' ?? 'default'; // '' (string vazia não é nullish)
// Comparar com || (verificação de falsy)
const valueOr = null || 'default'; // «default»
const zeroOr = 0 || 'default'; // «default» (0 é falsy!)
// Combinação
const setting = config?.theme ?? 'light';
Async/Await
// Função async básica
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user;
}
// Tratamento de erros
async function fetchUserSafe(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch user:', error);
return null;
}
}
// Execução em paralelo
async function fetchAll() {
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
return { users, posts };
}
// Execução sequencial
async function fetchSequential() {
const user = await fetchUser(1);
const posts = await fetchPosts(user.id); // Depende do user
return { user, posts };
}
// Arrow function async
const getUser = async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};
Módulos (Import/Export)
// Exports nomeados (utils.js)
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator { }
// Export por defeito (User.js)
export default class User {
constructor(name) {
this.name = name;
}
}
// Imports nomeados
import { PI, add } from './utils.js';
// Renomear imports
import { add as sum } from './utils.js';
// Importar tudo como namespace
import * as utils from './utils.js';
utils.add(1, 2);
// Import por defeito
import User from './User.js';
// Misto
import User, { helper } from './User.js';
// Import dinâmico
const module = await import('./heavy-module.js');
Principais Conclusões
- Use const por defeito: Só use let quando for necessária reatribuição
- Faça destructuring cedo: Simplifique parâmetros de funções e atribuições
- Spread para imutabilidade: Crie novos arrays/objetos em vez de os mutar
- Arrow functions para callbacks: Sintaxe mais limpa, this lexical
- Optional chaining para segurança: Evite «cannot read property of undefined»
- async/await em vez de promises: Código async mais legível
As funcionalidades modernas de JavaScript não são apenas açúcar sintático — permitem padrões que tornam o código mais robusto e fácil de manter.