İçeriğe geç
JavaScript

JavaScript Performans İpuçları: Hız Optimizasyonu

Tarık Tunç
JavaScript Performans İpuçları: Hız Optimizasyonu

JavaScript Performans İpuçları: Hız Optimizasyonu

JavaScript performansı, kullanıcı deneyimini doğrudan etkiler. Yavaş çalışan kod, sayfa etkileşimlerinde gecikmeye, düşük FPS'e ve olumsuz Core Web Vitals skorlarına yol açar. Bu rehberde JavaScript performansını artırmak için DOM manipülasyonu, olay yönetimi, bellek optimizasyonu, algoritma verimliliği ve modern tarayıcı API'lerini inceleyeceğiz.

DOM Manipülasyonu Optimizasyonu

Batch DOM Güncellemeleri

// Kötü: Her iterasyonda DOM'a erişim (reflow/repaint)
function addItemsBad(items) {
  const list = document.getElementById('list');
  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    list.appendChild(li); // Her seferinde reflow
  });
}

// İyi: DocumentFragment ile toplu ekleme
function addItemsGood(items) {
  const list = document.getElementById('list');
  const fragment = document.createDocumentFragment();

  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    fragment.appendChild(li);
  });

  list.appendChild(fragment); // Tek reflow
}

// Daha iyi: innerHTML ile toplu ekleme (küçük listeler için)
function addItemsBest(items) {
  const list = document.getElementById('list');
  list.innerHTML = items
    .map(item => `<li>${escapeHtml(item)}</li>`)
    .join('');
}

Layout Thrashing'den Kaçınma

// Kötü: Layout thrashing (okuma/yazma karışık)
function resizeAll(elements) {
  elements.forEach(el => {
    const height = el.offsetHeight; // Okuma (reflow tetikler)
    el.style.height = height * 2 + 'px'; // Yazma
  });
}

// İyi: Önce oku, sonra yaz
function resizeAllOptimized(elements) {
  // Toplu okuma
  const heights = elements.map(el => el.offsetHeight);

  // Toplu yazma
  elements.forEach((el, i) => {
    el.style.height = heights[i] * 2 + 'px';
  });
}

// En iyi: requestAnimationFrame kullanımı
function resizeAllRAF(elements) {
  const heights = elements.map(el => el.offsetHeight);

  requestAnimationFrame(() => {
    elements.forEach((el, i) => {
      el.style.height = heights[i] * 2 + 'px';
    });
  });
}
Bilgi: Layout thrashing, DOM'dan okuma ve yazma işlemlerinin karışık yapılmasıdır. Tarayıcı her okumada layout'u yeniden hesaplar. Okumaları ve yazmaları gruplayarak bu maliyetten kaçının.

Olay (Event) Optimizasyonu

Debounce ve Throttle

// Debounce: Son çağrıdan belirli süre sonra çalışır
function debounce(fn, delay) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Throttle: Belirli aralıklarla en fazla bir kez çalışır
function throttle(fn, limit) {
  let inThrottle = false;
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

// Kullanım
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
  fetchSearchResults(e.target.value);
}, 300));

window.addEventListener('scroll', throttle(() => {
  updateScrollPosition();
}, 100));

Event Delegation

// Kötü: Her elemente ayrı listener
document.querySelectorAll('.btn').forEach(btn => {
  btn.addEventListener('click', handleClick); // 100 listener
});

// İyi: Tek listener, event delegation
document.getElementById('button-container').addEventListener('click', (e) => {
  const btn = e.target.closest('.btn');
  if (btn) handleClick(e);
}); // 1 listener

Bellek Optimizasyonu

// Hafıza sızıntısı: Olay dinleyicilerini temizleme
class Component {
  constructor() {
    this.handleResize = this.onResize.bind(this);
    window.addEventListener('resize', this.handleResize);
  }

  onResize() { /* ... */ }

  // Mutlaka cleanup yapın
  destroy() {
    window.removeEventListener('resize', this.handleResize);
  }
}

// AbortController ile kolay cleanup
class ModernComponent {
  constructor() {
    this.controller = new AbortController();
    const { signal } = this.controller;

    window.addEventListener('resize', this.onResize, { signal });
    window.addEventListener('scroll', this.onScroll, { signal });
    document.addEventListener('click', this.onClick, { signal });
  }

  destroy() {
    this.controller.abort(); // Tüm listener'ları tek seferde temizle
  }
}

// Timer temizleme
let intervalId = setInterval(update, 1000);
// Kullanılmadığında temizle
clearInterval(intervalId);
intervalId = null;
Uyarı: Temizlenmeyen event listener'lar, timer'lar ve closure referansları en yaygın hafıza sızıntısı kaynaklarıdır. Bileşen kaldırıldığında veya sayfa değiştiğinde cleanup yapmayı unutmayın.

Döngü ve Algoritma Optimizasyonu

// Dizi arama: O(n) vs O(1)
// Kötü: Her arama O(n)
const items = [{ id: 1, name: 'a' }, { id: 2, name: 'b' }, /* ... 10000 */];
function findById(id) {
  return items.find(item => item.id === id); // O(n)
}

// İyi: Map ile O(1) arama
const itemMap = new Map(items.map(item => [item.id, item]));
function findByIdFast(id) {
  return itemMap.get(id); // O(1)
}

// Set ile hızlı varlık kontrolü
const activeIds = new Set([1, 5, 10, 25, 100]);
activeIds.has(25); // O(1) - includes() O(n)'den çok daha hızlı

// Erken çıkış ile gereksiz iterasyondan kaçınma
function hasAdminUser(users) {
  // some() bulduğunda durur, forEach tamamı döner
  return users.some(user => user.role === 'admin');
}

Lazy Loading ve Code Splitting

// Dinamik import
async function loadChart() {
  const { Chart } = await import('chart.js');
  return new Chart(/* ... */);
}

// IntersectionObserver ile lazy loading
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.remove('lazy');
      observer.unobserve(img);
    }
  });
}, { rootMargin: '200px' });

document.querySelectorAll('img.lazy').forEach(img => {
  observer.observe(img);
});

requestIdleCallback ile Düşük Öncelikli İşler

// Boş zamanlarda çalıştır (ana thread'i bloklamaz)
function processLargeData(data) {
  let index = 0;

  function processChunk(deadline) {
    while (index < data.length && deadline.timeRemaining() > 0) {
      processItem(data[index]);
      index++;
    }

    if (index < data.length) {
      requestIdleCallback(processChunk);
    } else {
      console.log('İşlem tamamlandı');
    }
  }

  requestIdleCallback(processChunk);
}

Performans Ölçümü

// Performance API ile ölçüm
performance.mark('start-process');

// ... işlem ...

performance.mark('end-process');
performance.measure('process-duration', 'start-process', 'end-process');

const measure = performance.getEntriesByName('process-duration')[0];
console.log(`İşlem süresi: ${measure.duration.toFixed(2)}ms`);

// PerformanceObserver ile sürekli izleme
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    if (entry.duration > 50) {
      console.warn('Yavaş işlem:', entry.name, entry.duration + 'ms');
    }
  });
});
observer.observe({ entryTypes: ['measure', 'longtask'] });
İpucu: Chrome DevTools Performance sekmesinde recording yaparak hangi fonksiyonların en fazla süre harcadığını tespit edin. "Long tasks" (>50ms) özellikle INP metriğini olumsuz etkiler.

Modern API'ler ile Performans

// structuredClone (derin kopya - JSON.parse'dan hızlı)
const clone = structuredClone(complexObject);

// Array.at() (negatif indeks desteği)
const last = arr.at(-1); // arr[arr.length - 1] yerine

// Object.groupBy (ES2024)
const grouped = Object.groupBy(users, user => user.role);

// Promise.withResolvers (ES2024)
const { promise, resolve, reject } = Promise.withResolvers();

Sonuç

JavaScript performansı, DOM manipülasyonlarını minimize etme, olay yönetimini optimize etme, bellek sızıntılarını önleme ve doğru veri yapılarını seçme ile iyileştirilir. Debounce/throttle ile gereksiz çağrıları engelleyin, event delegation ile listener sayısını azaltın, Map/Set ile arama performansını artırın. Performance API ile darboğazları tespit edin ve hedefli optimizasyon yapın.

Kaynaklar

İlgili Yazılar