Performance

JavaScript Otimizado

Técnicas para carregar e executar JavaScript de forma eficiente.

Por que Otimizar JavaScript?

JavaScript pode ser um grande gargalo de performance:

  • Bloqueio de parsing: Scripts bloqueiam renderização
  • Tamanho: Bundles grandes aumentam tempo de download
  • Execução: Código pesado bloqueia thread principal
  • First Input Delay: JavaScript pode causar atraso na interação

Carregamento de Scripts

Async vs Defer

<!-- ❌ Bloqueia parsing -->
<script src="script.js"></script>

<!-- ✅ Async - executa assim que baixar (não mantém ordem) -->
<script src="script.js" async></script>

<!-- ✅ Defer - executa após parsing (mantém ordem) -->
<script src="script.js" defer></script>

Quando Usar Cada Um

Async: Para scripts independentes (analytics, ads)

<script src="analytics.js" async></script>

Defer: Para scripts que dependem do DOM

<script src="app.js" defer></script>

Scripts Críticos Inline

Para JavaScript crítico necessário imediatamente:

<script>
  // Código crítico inline (pequeno)
  document.documentElement.classList.add('js-enabled');
</script>

Code Splitting

Dynamic Imports

Divida código em chunks menores:

// ❌ Ruim - tudo em um bundle
import { heavyFunction } from './heavy-module';
heavyFunction();

// ✅ Bom - carregamento sob demanda
button.addEventListener('click', async () => {
  const { heavyFunction } = await import('./heavy-module');
  heavyFunction();
});

Route-based Splitting

// React Router
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

<Suspense fallback={<Loading />}>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/about" element={<About />} />
  </Routes>
</Suspense>

Component-based Splitting

// Componente pesado carregado sob demanda
const Chart = lazy(() => import('./components/Chart'));

function Dashboard() {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <>
      <button onClick={() => setShowChart(true)}>
        Mostrar Gráfico
      </button>
      {showChart && (
        <Suspense fallback={<ChartSkeleton />}>
          <Chart />
        </Suspense>
      )}
    </>
  );
}

Minificação e Compressão

Minificação

// Antes (desenvolvimento)
function calculateTotal(items) {
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    total += items[i].price;
  }
  return total;
}

// Depois (produção)
function calculateTotal(a){let b=0;for(let c=0;c<a.length;c++)b+=a[c].price;return b}

Tree Shaking

Remova código não utilizado:

// ❌ Ruim - importa tudo
import * as utils from './utils';

// ✅ Bom - importa apenas o necessário
import { debounce, throttle } from './utils';

Configuração de Build

// webpack.config.js
module.exports = {
  optimization: {
    usedExports: true, // Tree shaking
    minimize: true, // Minificação
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
};

Preload e Prefetch

Preload de Scripts Críticos

<!-- Preload script crítico -->
<link rel="preload" as="script" href="critical.js">
<script src="critical.js"></script>

Prefetch de Scripts Futuros

<!-- Prefetch script que será usado depois -->
<link rel="prefetch" as="script" href="next-page.js">

Resource Hints

<!-- DNS prefetch -->
<link rel="dns-prefetch" href="https://api.example.com">

<!-- Preconnect (DNS + TCP + TLS) -->
<link rel="preconnect" href="https://api.example.com">

Otimização de Execução

Debounce e Throttle

// Debounce - executa após parar de chamar
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// Uso
const handleSearch = debounce((query) => {
  searchAPI(query);
}, 300);

input.addEventListener('input', (e) => {
  handleSearch(e.target.value);
});
// Throttle - executa no máximo uma vez por período
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// Uso
const handleScroll = throttle(() => {
  updatePosition();
}, 100);

window.addEventListener('scroll', handleScroll);

Lazy Evaluation

// ❌ Ruim - calcula sempre
function expensiveCalculation() {
  return heavyComputation();
}

// ✅ Bom - calcula apenas quando necessário
let cachedResult = null;
function expensiveCalculation() {
  if (cachedResult === null) {
    cachedResult = heavyComputation();
  }
  return cachedResult;
}

Web Workers

Mova processamento pesado para worker:

// main.js
const worker = new Worker('worker.js');

worker.postMessage({ data: largeArray });

worker.onmessage = (e) => {
  const result = e.data;
  // Usar resultado
};

// worker.js
self.onmessage = (e) => {
  const { data } = e.data;
  const result = heavyProcessing(data);
  self.postMessage(result);
};

Otimização de Event Listeners

Event Delegation

// ❌ Ruim - muitos listeners
document.querySelectorAll('.button').forEach(button => {
  button.addEventListener('click', handleClick);
});

// ✅ Bom - um listener
document.addEventListener('click', (e) => {
  if (e.target.matches('.button')) {
    handleClick(e);
  }
});

Passive Listeners

Para eventos de scroll/touch que não precisam preventDefault:

// ✅ Melhor performance
window.addEventListener('scroll', handleScroll, { passive: true });

Bundle Analysis

Analisar Tamanho de Bundles

// webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  .BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

Identificar Dependências Pesadas

# Analisar tamanho de node_modules
npx bundle-phobia [package-name]

# Verificar duplicações
npx webpack-bundle-analyzer dist/stats.json

Checklist de Otimização

  • Scripts com async/defer quando apropriado
  • Code splitting implementado
  • Tree shaking configurado
  • Minificação habilitada
  • Gzip/Brotli compression
  • Preload para scripts críticos
  • Prefetch para scripts futuros
  • Event delegation onde possível
  • Debounce/throttle em eventos frequentes
  • Web Workers para processamento pesado

Exemplo Completo

<!DOCTYPE html>
<html>
<head>
  <!-- Preconnect para APIs -->
  <link rel="preconnect" href="https://api.example.com">
  
  <!-- Preload script crítico -->
  <link rel="preload" as="script" href="/js/critical.js">
  
  <!-- Prefetch script da próxima página -->
  <link rel="prefetch" as="script" href="/js/next-page.js">
</head>
<body>
  <!-- Conteúdo -->
  
  <!-- Script crítico inline pequeno -->
  <script>
    // Inicialização crítica
    document.documentElement.classList.add('js-enabled');
  </script>
  
  <!-- Script crítico -->
  <script src="/js/critical.js"></script>
  
  <!-- Scripts não críticos com defer -->
  <script src="/js/app.js" defer></script>
  
  <!-- Analytics com async -->
  <script src="/js/analytics.js" async></script>
</body>
</html>
// app.js - com code splitting
const loadChart = async () => {
  const { Chart } = await import('./components/Chart');
  return Chart;
};

// Lazy load componente pesado
button.addEventListener('click', async () => {
  const Chart = await loadChart();
  renderChart(Chart);
});
Meça o impacto de cada otimização. Nem toda otimização vale a pena - foque nas que têm maior impacto na experiência do usuário.