Otimização de desempenho em JavaScript
Técnicas e boas práticas para criar aplicações JavaScript rápidas e eficientes.
InSkillCoach
Otimização de desempenho em JavaScript
O desempenho é um aspecto crucial do desenvolvimento web moderno. Aplicações lentas frustram os usuários e podem ter um impacto negativo nas conversões, no SEO e na experiência geral. Este artigo explora as técnicas de otimização de desempenho em JavaScript, dos fundamentos às estratégias avançadas.
Por que o desempenho é importante?
- Experiência do usuário: os usuários abandonam sites lentos (53% saem de uma página que leva mais de 3 segundos para carregar)
- SEO: o Google usa a velocidade como fator de ranqueamento
- Conversão: cada segundo de atraso pode reduzir as conversões em 7%
- Acessibilidade: dispositivos e conexões de baixo desempenho se beneficiam muito das otimizações
- Custos: um código eficiente consome menos recursos do servidor
Entendendo o desempenho do JavaScript
O motor JavaScript
Para otimizar de forma eficaz, é útil entender como funciona o motor JavaScript:
- Parsing: transformação do código em Abstract Syntax Tree (AST)
- Compilação: conversão em bytecode e depois em código de máquina otimizado
- Execução: execução do código com coleta de dados para otimizações futuras
- Garbage Collection: liberação da memória não utilizada
Principais métricas de desempenho
- First Contentful Paint (FCP): primeira exibição de conteúdo
- Largest Contentful Paint (LCP): exibição do conteúdo principal
- First Input Delay (FID): atraso antes da interação
- Cumulative Layout Shift (CLS): estabilidade visual
- Time to Interactive (TTI): momento em que a página se torna interativa
- Total Blocking Time (TBT): tempo durante o qual a thread principal fica bloqueada
Otimizações fundamentais
1. Minimizar as manipulações do DOM
O DOM é lento. Toda vez que você modifica o DOM, o navegador precisa recalcular os estilos, o layout e, às vezes, repintar a tela.
// Ineficiente: múltiplas manipulações do DOM
for (let i = 0; i < 1000; i++) {
document.body.innerHTML += `<div>${i}</div>`;
}
// Otimizado: manipulação única do DOM
let html = '';
for (let i = 0; i < 1000; i++) {
html += `<div>${i}</div>`;
}
document.body.innerHTML += html;
// Ainda melhor: usar DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = i;
fragment.appendChild(div);
}
document.body.appendChild(fragment);
2. Evitar reflows e repaints
- Reflow: recálculo completo da geometria dos elementos (custoso)
- Repaint: atualização visual sem mudança de geometria (menos custoso)
// Ineficiente: provoca vários reflows
const element = document.getElementById('monElement');
element.style.width = '100px';
element.style.height = '200px';
element.style.margin = '10px';
// Otimizado: um único reflow
const element = document.getElementById('monElement');
element.classList.add('nouvelles-dimensions');
// Ou com style
element.style.cssText = 'width: 100px; height: 200px; margin: 10px;';
3. Usar eventos de forma eficiente
// Ineficiente: um handler por elemento
document.querySelectorAll('button').forEach(button => {
button.addEventListener('click', handleClick);
});
// Otimizado: delegação de eventos
document.addEventListener('click', e => {
if (e.target.matches('button')) {
handleClick(e);
}
});
4. Otimizar os laços
const arr = new Array(1000000).fill(1);
// Ineficiente: acesso ao tamanho a cada iteração
for (let i = 0; i < arr.length; i++) {
// processamento
}
// Otimizado: armazenamento do tamanho
const len = arr.length;
for (let i = 0; i < len; i++) {
// processamento
}
// Moderno: métodos de array
arr.forEach(item => {
// processamento
});
// Frequentemente o mais rápido para arrays simples
for (const item of arr) {
// processamento
}
5. Memoization para funções custosas
// Sem memoization
function calculComplexe(n) {
console.log('Calcul pour', n);
// Simulação de um cálculo custoso
return n * n;
}
// Com memoization
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const calculComplexeMemoize = memoize(calculComplexe);
// Primeira chamada: cálculo realizado
calculComplexeMemoize(42);
// Segunda chamada com o mesmo argumento: resultado em cache
calculComplexeMemoize(42);
Otimizações avançadas
1. Web Workers para tarefas intensivas
Os Web Workers permitem executar código JavaScript em uma thread separada, evitando bloquear a interface do usuário.
// main.js
const worker = new Worker('worker.js');
worker.onmessage = function(e) {
console.log('Résultat reçu du worker:', e.data);
};
worker.postMessage({ data: complexData, operation: 'process' });
// worker.js
self.onmessage = function(e) {
const { data, operation } = e.data;
if (operation === 'process') {
// Processamento intensivo que não bloqueia a UI
const result = processData(data);
self.postMessage(result);
}
};
2. Virtualização de listas longas
Para listas muito longas, renderize apenas os elementos visíveis na área de exibição.
// Exemplo simplificado de virtualização
function renderVisibleItems(items, container, itemHeight) {
const scrollTop = window.scrollY;
const viewportHeight = window.innerHeight;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
items.length - 1,
Math.floor((scrollTop + viewportHeight) / itemHeight)
);
container.style.height = `${items.length * itemHeight}px`;
container.innerHTML = '';
for (let i = startIndex; i <= endIndex; i++) {
const item = document.createElement('div');
item.textContent = items[i];
item.style.position = 'absolute';
item.style.top = `${i * itemHeight}px`;
item.style.height = `${itemHeight}px`;
container.appendChild(item);
}
}
// Uso com um listener de rolagem
window.addEventListener('scroll', () => {
renderVisibleItems(myLargeArray, myContainer, 50);
});
3. Uso eficiente de timers
// Ineficiente: múltiplos timers
function inefficientAnimation() {
for (let i = 0; i < 100; i++) {
setTimeout(() => updateElement(i), i * 10);
}
}
// Otimizado: requestAnimationFrame
function efficientAnimation() {
let i = 0;
function step() {
updateElement(i);
i++;
if (i < 100) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}
4. Debounce e Throttle
// Debounce: executa a função após um intervalo sem chamadas
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
// Throttle: limita o número de chamadas ao longo do tempo
function throttle(fn, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
fn.apply(this, args);
lastCall = now;
}
};
}
// Uso
const debouncedResize = debounce(() => {
console.log('Redimensionnement terminé');
}, 200);
const throttledScroll = throttle(() => {
console.log('Défilement contrôlé');
}, 100);
window.addEventListener('resize', debouncedResize);
window.addEventListener('scroll', throttledScroll);
Otimização do carregamento
1. Code splitting
Divida seu código em partes menores para carregar apenas o que é necessário.
// Carregamento dinâmico com import()
button.addEventListener('click', async () => {
const module = await import('./feature.js');
module.default();
});
2. Lazy loading
// Lazy loading de imagens
document.querySelectorAll('img[data-src]').forEach(img => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
img.src = img.dataset.src;
observer.disconnect();
}
});
});
observer.observe(img);
});
// Lazy loading de componentes
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<React.Suspense fallback={<div>Chargement...</div>}>
<LazyComponent />
</React.Suspense>
);
}
3. Preload, Prefetch e Preconnect
<!-- Preload: recurso crítico para a página atual -->
<link rel="preload" href="critical.js" as="script">
<!-- Prefetch: recurso para a navegação futura -->
<link rel="prefetch" href="next-page.js" as="script">
<!-- Preconnect: estabelecer uma conexão antecipada -->
<link rel="preconnect" href="https://api.example.com">
Medição e monitoramento do desempenho
1. Performance API
// Medição do tempo de execução
performance.mark('debut');
// Código a ser medido
performance.mark('fin');
performance.measure('monOperation', 'debut', 'fin');
const mesures = performance.getEntriesByType('measure');
console.log(mesures[0].duration);
// Navigation Timing API
window.addEventListener('load', () => {
const timing = performance.timing;
const pageLoadTime = timing.loadEventEnd - timing.navigationStart;
console.log(`Temps de chargement: ${pageLoadTime}ms`);
});
2. Ferramentas de desenvolvimento
- Chrome DevTools: abas Performance, Network, Memory
- Lighthouse: auditoria automatizada de desempenho
- WebPageTest: testes de desempenho detalhados
- Bundle analyzers: análise do tamanho dos bundles
Boas práticas gerais
- Minificar e comprimir os arquivos JavaScript
- Usar o cache do navegador de forma eficiente
- Evitar bibliotecas desnecessárias e preferir soluções nativas
- Adotar os recursos modernos do JavaScript (mas verificar a compatibilidade)
- Fazer profiling regularmente do seu código para identificar gargalos
- Otimizar imagens e mídias para reduzir a carga total
- Usar uma CDN para os recursos estáticos
- Implementar Server-Side Rendering ou geração estática sempre que possível
Armadilhas comuns a evitar
- Otimização prematura em excesso: meça primeiro, otimize depois
- Microotimizações excessivas: concentre-se nos ganhos significativos
- Ignorar a experiência mobile: teste em dispositivos reais e conexões lentas
- Negligenciar a acessibilidade: as otimizações não devem comprometer a acessibilidade
- Esquecer de testar após a otimização: verifique se tudo ainda funciona corretamente
Conclusão
A otimização de desempenho em JavaScript é um equilíbrio entre velocidade, manutenibilidade e experiência do usuário. Comece pelas otimizações de maior impacto, meça os resultados e depois refine progressivamente.
Lembre-se de que a melhor otimização costuma ser o código que você não escreve. Antes de adicionar uma nova biblioteca ou funcionalidade, avalie seu impacto no desempenho e se ela é realmente necessária.
Seguindo essas técnicas e boas práticas, você poderá criar aplicações JavaScript rápidas, responsivas e agradáveis de usar, mesmo em dispositivos e conexões modestos.
Sobre InSkillCoach
Especialista em treinamento e tecnologias
Coach especializado em tecnologias avançadas e IA, apoiado pela GNeurone Inc.
Certificações:
- AWS Certified Solutions Architect – Professional
- Certificações Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comentários
Os comentários são alimentados pelo GitHub Discussions
Conecte-se com o GitHub para participar da discussão