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.