0%
Otimização de desempenho em JavaScript

Otimização de desempenho em JavaScript

Técnicas e boas práticas para criar aplicações JavaScript rápidas e eficientes.

I

InSkillCoach

· min

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:

  1. Parsing: transformação do código em Abstract Syntax Tree (AST)
  2. Compilação: conversão em bytecode e depois em código de máquina otimizado
  3. Execução: execução do código com coleta de dados para otimizações futuras
  4. 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

  1. Minificar e comprimir os arquivos JavaScript
  2. Usar o cache do navegador de forma eficiente
  3. Evitar bibliotecas desnecessárias e preferir soluções nativas
  4. Adotar os recursos modernos do JavaScript (mas verificar a compatibilidade)
  5. Fazer profiling regularmente do seu código para identificar gargalos
  6. Otimizar imagens e mídias para reduzir a carga total
  7. Usar uma CDN para os recursos estáticos
  8. Implementar Server-Side Rendering ou geração estática sempre que possível

Armadilhas comuns a evitar

  1. Otimização prematura em excesso: meça primeiro, otimize depois
  2. Microotimizações excessivas: concentre-se nos ganhos significativos
  3. Ignorar a experiência mobile: teste em dispositivos reais e conexões lentas
  4. Negligenciar a acessibilidade: as otimizações não devem comprometer a acessibilidade
  5. 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.

InSkillCoach

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+
644
131

Comentários

Os comentários são alimentados pelo GitHub Discussions

Conecte-se com o GitHub para participar da discussão

Link copiado!