7 zaawansowanych technik JavaScript,
które powinien znać
każdy programista

JavaScript nieustannie się rozwija, oferując programistom coraz więcej potężnych narzędzi do pisania czystego, szybkiego i efektywnego kodu. Jednak przy takiej obfitości funkcji i technologii łatwo przeoczyć te najważniejsze. Jeśli twoim celem jest poprawa wydajności lub uproszczenie utrzymania kodu, te zaawansowane techniki dadzą ci ogromną przewagę. Przyjrzyjmy się 7 zaawansowanym technikom w JavaScript, które pomogą ci podnieść swoje umiejętności na wyższy poziom.

1. Zamknięcia uczynią twój kod czystszym

Zamknięcia to jedna z najpotężniejszych, ale często źle rozumianych funkcji w JavaScript. Dzięki nim można tworzyć funkcje z prywatnymi zmiennymi, co sprawia, że kod staje się bardziej strukturalny i bezpieczny.

Czym jest zamknięcie? To funkcja, która „pamięta” swoją przestrzeń nazw, nawet po zakończeniu swojego działania. Jest to przydatne, gdy chcemy przechowywać stan wewnątrz funkcji, unikając globalnych zmiennych.


// Przykład zamknięcia
function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
    

Przykłady użycia: zamknięcia są idealne do przechowywania stanu w obsługiwaniu zdarzeń, tworzenia prywatnych zmiennych lub pisania funkcji wyższego rzędu.

2. Destrukturyzacja upraszcza kod

Destrukturyzacja to wygodna funkcja wprowadzona w ES6, która pozwala wyciągać wartości z tablic lub obiektów i przypisywać je do zmiennych w bardziej zwięzły i czytelny sposób. Ułatwia to pisanie kodu oraz poprawia jego czytelność i utrzymanie.


// Destrukturyzacja obiektu
const person = { name: 'Alice', age: 30 };
const { name, age } = person;

console.log(name); // 'Alice'
console.log(age);  // 30

// Destrukturyzacja tablicy
const numbers = [1, 2, 3];
const [first, second] = numbers;

console.log(first);  // 1
console.log(second); // 2
    

Przykłady użycia: destrukturyzacja jest szczególnie przydatna podczas pracy z API lub złożonymi obiektami, gdy potrzebujemy wyciągnąć tylko niezbędne dane.

3. Debouncing i Throttling — jak zoptymalizować wydajność

Przy obsłudze zdarzeń użytkownika, takich jak przewijanie lub zmiana rozmiaru okna, częste wywoływanie funkcji dla każdego działania może znacznie spowolnić aplikację. Debouncing i Throttling to dwa sposoby kontrolowania częstotliwości wywoływania funkcji.

Debouncing — funkcja wywołuje się dopiero po zakończeniu akcji użytkownika i upłynięciu określonego czasu bez nowych zdarzeń.

Throttling — funkcja wywołuje się nie częściej niż raz na określony czas.


// Funkcja debounce
function debounce(func, delay) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), delay);
  };
}

// Funkcja throttle
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}
    

Te techniki doskonale sprawdzają się przy optymalizacji wydajności podczas wprowadzania tekstu do pola wyszukiwania, obsługi zdarzeń przewijania lub zmiany rozmiaru okna.

4. Karrowanie zwiększa elastyczność funkcji

Karrowanie to technika przekształcania funkcji przyjmującej wiele argumentów w łańcuch funkcji, z których każda przyjmuje jeden argument. Dzięki temu funkcje stają się bardziej elastyczne i można je częściowo stosować.


// Podstawowa funkcja curry
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

// Zastosowanie
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 6
    

Karrowanie jest szczególnie przydatne przy tworzeniu złożonych funkcji, które mogą być używane z częściowymi danymi, na przykład w programowaniu funkcyjnym lub w komponentach React.

5. Proxy przechwytuje działania na obiektach

Proxy umożliwia przechwytywanie i modyfikowanie zachowania obiektów, takich jak dostęp do ich właściwości, ich modyfikacja lub wywoływanie funkcji. Jest to przydatne do walidacji danych, logowania lub tworzenia systemów reaktywnych.


const person = {
  name: 'John',
  age: 25
};

const handler = {
  get: function(target, property) {
    console.log(`Pobieranie właściwości ${property}`);
    return property in target ? target[property] : 'Właściwość nie znaleziona';
  },
  set: function(target, property, value) {
    if (property === 'age' && value < 0) {
      console.error('Wiek nie może być ujemny');
    } else {
      target[property] = value;
    }
  }
};

const proxyPerson = new Proxy(person, handler);
console.log(proxyPerson.name); // Zaloguje "Pobieranie właściwości name" i zwróci "John"
proxyPerson.age = -5; // Zaloguje "Wiek nie może być ujemny"
    

Proxy często używany jest do walidacji danych, w takich frameworkach jak Vue.js, a także do monitorowania dostępu do poufnych informacji.

6. Jak działa Event Loop i asynchroniczny JavaScript

JavaScript jest językiem jednowątkowym, co oznacza, że w danym momencie może wykonywać tylko jedno zadanie. Jednak dzięki pętli zdarzeń (Event Loop) operacje asynchroniczne mogą być wykonywane efektywnie, bez blokowania głównego wątku.

Zrozumienie pętli zdarzeń jest niezbędne do pisania wydajnego asynchronicznego kodu, szczególnie jeśli pracujesz z setTimeout, Promises lub async/await.


console.log('Start');

setTimeout(() => {
  console.log('Inside setTimeout');
}, 0);

Promise.resolve().then(() => {
  console.log('Inside Promise');
});

console.log('End');

// Output: 
// Start
// End
// Inside Promise
// Inside setTimeout
    

Znajomość działania pętli zdarzeń jest kluczowa przy tworzeniu aplikacji, obsłudze zapytań API i zarządzaniu zadaniami asynchronicznymi.

7. Memoizacja dla poprawy wydajności

Memoizacja to technika, która umożliwia przechowywanie wyników zasobożernych funkcji i zwracanie ich z pamięci podręcznej podczas kolejnych wywołań z tymi samymi argumentami. Zwiększa to szybkość funkcji, które często są wywoływane z tymi samymi danymi wejściowymi.


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.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// Zastosowanie
const slowFunction = (num) => {
  console.log('Długa operacja...');
  return num * 2;
};

const memoizedFunction = memoize(slowFunction);
console.log(memoizedFunction(5)); // Długa operacja... 10
console.log(memoizedFunction(5)); // 10 (z pamięci podręcznej)
    

Memoizacja jest szczególnie przydatna do optymalizacji skomplikowanych obliczeń w aplikacjach, które przetwarzają duże ilości danych, na przykład przy sortowaniu tablic lub wykonywaniu operacji matematycznych.

Opanowanie tych zaawansowanych technik JavaScript pomoże ci pisać bardziej czysty, szybki i wydajny kod. Te metody poprawią wydajność, czytelność oraz umożliwią budowanie skalowalnych aplikacji, podnosząc twoje umiejętności JavaScript na wyższy poziom.