JavaScript Performance Optimization

Wydajność JavaScript ma kluczowe znaczenie dla doświadczenia użytkownika. W tym artykule poznasz praktyczne techniki optymalizacji, które pomogą Ci stworzyć szybsze i bardziej responsywne aplikacje.

Dlaczego wydajność JavaScript jest ważna?

Wydajność JavaScript wpływa bezpośrednio na:

  • Szybkość ładowania strony
  • Responsywność interfejsu użytkownika
  • Zużycie baterii na urządzeniach mobilnych
  • Pozycjonowanie w wyszukiwarkach (SEO)
  • Ogólne doświadczenie użytkownika

1. Optymalizacja ładowania kodu

Używaj defer i async dla skryptów

Właściwe załadowanie skryptów może znacznie przyspieszyć renderowanie strony:


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


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


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

Podział kodu (Code Splitting)

Ładuj tylko kod, który jest aktualnie potrzebny:

// Dynamiczny import - ładowanie na żądanie
async function loadModule() {
    const module = await import('./heavy-module.js');
    module.doSomething();
}

// Warunkowo ładuj moduły
if (needsFeature) {
    import('./feature-module.js').then(module => {
        module.initializeFeature();
    });
}

2. Optymalizacja manipulacji DOM

Minimalizuj dostęp do DOM

Każde odwołanie do DOM jest kosztowne. Przechowuj referencje:

// Źle - wielokrotne zapytania DOM
function updateElements() {
    document.getElementById('title').textContent = 'Nowy tytuł';
    document.getElementById('title').style.color = 'blue';
    document.getElementById('title').classList.add('active');
}

// Dobrze - jedna referencja
function updateElements() {
    const title = document.getElementById('title');
    title.textContent = 'Nowy tytuł';
    title.style.color = 'blue';
    title.classList.add('active');
}

Używaj DocumentFragment dla wielu zmian

DocumentFragment pozwala na batch'owanie zmian DOM:

// Źle - wielokrotne manipulacje DOM
function createList(items) {
    const list = document.getElementById('list');
    items.forEach(item => {
        const li = document.createElement('li');
        li.textContent = item;
        list.appendChild(li); // Reflow za każdym razem
    });
}

// Dobrze - użycie DocumentFragment
function createList(items) {
    const fragment = document.createDocumentFragment();
    items.forEach(item => {
        const li = document.createElement('li');
        li.textContent = item;
        fragment.appendChild(li);
    });
    document.getElementById('list').appendChild(fragment); // Jeden reflow
}

3. Optymalizacja pętli i operacji na danych

Wybierz właściwą metodę iteracji

Różne metody iteracji mają różną wydajność:

const items = [1, 2, 3, 4, 5];

// Najszybsza - klasyczna pętla for
for (let i = 0; i < items.length; i++) {
    console.log(items[i]);
}

// Szybka - for...of
for (const item of items) {
    console.log(item);
}

// Wolniejsza - forEach (ale bardziej czytelna)
items.forEach(item => console.log(item));

// Najwolniejsza - for...in (dla obiektów)
for (const index in items) {
    console.log(items[index]);
}

Optymalizacja operacji na arrayach

Używaj właściwych metod dla różnych zastosowań:

// Znajdowanie pojedynczego elementu
const user = users.find(u => u.id === targetId);

// Sprawdzanie czy element istnieje
const hasUser = users.some(u => u.id === targetId);

// Filtrowanie z wcześniejszym przerwaniem
const activeUsers = users.filter(u => u.active);

// Mapowanie z optymalizacją
const userNames = users.map(u => u.name);

// Redukcja z akumulatorem
const totalAge = users.reduce((sum, u) => sum + u.age, 0);

4. Zarządzanie pamięcią

Unikaj wycieków pamięci

Pamiętaj o czyszczeniu referencji i detachów event listenerów:

class Component {
    constructor(element) {
        this.element = element;
        this.handleClick = this.handleClick.bind(this);
        this.element.addEventListener('click', this.handleClick);
    }

    handleClick(event) {
        console.log('Clicked!');
    }

    destroy() {
        // Usuń event listeners
        this.element.removeEventListener('click', this.handleClick);
        
        // Wyczyść referencje
        this.element = null;
        this.handleClick = null;
    }
}

Używaj WeakMap i WeakSet

Dla prywatnych danych komponentów:

const privateData = new WeakMap();

class Component {
    constructor(config) {
        privateData.set(this, {
            data: config.data,
            methods: config.methods
        });
    }

    getData() {
        return privateData.get(this).data;
    }

    // Gdy instancja zostanie usunięta, dane też zostaną automatycznie usunięte
}

5. Optymalizacja eventów

Używaj delegacji zdarzeń

Zamiast dodawać listener do każdego elementu:

// Źle - dużo event listenerów
document.querySelectorAll('.button').forEach(button => {
    button.addEventListener('click', handleClick);
});

// Dobrze - jeden delegowany listener
document.addEventListener('click', (event) => {
    if (event.target.classList.contains('button')) {
        handleClick(event);
    }
});

Throttling i debouncing

Ograniczaj częstotliwość wykonywania kosztownych operacji:

// Debouncing - wykonaj po zatrzymaniu
function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

// Throttling - wykonuj maksymalnie co X ms
function throttle(func, limit) {
    let inThrottle;
    return function executedFunction(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// Zastosowanie
window.addEventListener('scroll', throttle(handleScroll, 100));
searchInput.addEventListener('input', debounce(handleSearch, 300));

6. Optymalizacja animacji

Używaj requestAnimationFrame

Dla płynnych animacji zsynchronizowanych z odświeżaniem ekranu:

let start = null;
function animate(timestamp) {
    if (!start) start = timestamp;
    const progress = timestamp - start;
    
    // Aktualizuj animację
    element.style.transform = `translateX(${progress / 10}px)`;
    
    if (progress < 2000) {
        requestAnimationFrame(animate);
    }
}

requestAnimationFrame(animate);

Optymalizuj CSS dla animacji

Animuj właściwości, które nie powodują reflow:

/* Źle - powoduje reflow */
.element {
    transition: width 0.3s ease;
}

/* Dobrze - używa kompozycji */
.element {
    transition: transform 0.3s ease;
}

/* Jeszcze lepiej - z will-change */
.element {
    will-change: transform;
    transition: transform 0.3s ease;
}

7. Narzędzia do mierzenia wydajności

Performance API

// Mierzenie czasu wykonania
const start = performance.now();
// Twój kod
const end = performance.now();
console.log(`Wykonanie zajęło ${end - start} ms`);

// Monitoring zasobów
const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        console.log(`${entry.name}: ${entry.duration}ms`);
    }
});
observer.observe({ entryTypes: ['measure'] });

Web Vitals

// Monitoring Core Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);

8. Najlepsze praktyki

1. Minimalizuj rekurencję

Zastąp rekurencję iteracją tam gdzie to możliwe:

// Źle - rekurencja może prowadzić do stack overflow
function factorial(n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

// Dobrze - iteracja
function factorial(n) {
    let result = 1;
    for (let i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

2. Używaj const i let zamiast var

Nowoczesne deklaracje zmiennych są bardziej wydajne:

// Dobrze - const dla stałych
const API_URL = 'https://api.example.com';

// Dobrze - let dla zmiennych
let currentUser = null;

// Unikaj var
var oldStyleVariable = 'avoid this';

3. Optymalizuj stringi

// Dla wielu konkatenacji używaj template literals
const message = `Witaj ${user.name}, masz ${user.messages} wiadomości`;

// Lub join dla arrayów
const parts = ['część1', 'część2', 'część3'];
const result = parts.join(' ');

Podsumowanie

Optymalizacja wydajności JavaScript to proces ciągły, który powinien być integralną częścią procesu developmentu. Kluczowe jest:

  • Regularne profilowanie i mierzenie wydajności
  • Optymalizacja na podstawie rzeczywistych problemów, nie domniemań
  • Znajomość kosztów różnych operacji
  • Używanie właściwych narzędzi do właściwych zadań
  • Testowanie na różnych urządzeniach i przeglądarkach

Pamiętaj, że przedwczesna optymalizacja może być szkodliwa - zawsze najpierw pisz czytelny kod, a potem optymalizuj tam, gdzie to rzeczywiście potrzebne.