chevron-left chevron-right

[JS] Higher-order functions. Jak wydajnie pisać kod aplikacji webowych?

W języku skyptowym Javascript możemy tworzyć funkcje za pomocą wielu sposobów. Można utworzyć funkcje anonimowe, nazwane czy też samowykonywujące się. Każda z nich ma swoje wady i zalety, ale co ciekawsze istnieją też funkcje wyższego rzędu (tzw. higher-order functions). Co to jest za twór?

Higher-order functions - trochę teorii

Funkcja wyższego rzędzu jest to funkcja, która przyjmuje funkcję jako parametr funkcji oraz jako swój wynik zwraca inną funkcję. To tyle i aż tyle. Z tego typu funkcjami spotykamy się na codzień bardzo często nie mając o tym pojęcia, np. w bibliotece jQuery.

Zapewne to dalej brzmi tajemniczo, dlatego się postaram to nieco rozjasnić za pomocą przykładu.

Funkcja wyższego rzędu - przykład użycia

Przykład, będzie funkcją której zadaniem będzie zwrócenie wszystkich wartości wybranej własności w danym zbiorze, to znaczy, będziemy mieć tablicę użytkowników i będziemy chcieli wymienić ich z imienia.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
(function (window, document) { //#1
  var users = [ //#2
    { id: 1, name: 'Adam', age: 23},
    { id: 2, name: 'Marek', age: 55},
    { id: 3, name: 'Piotr', age: 20},
    { id: 4, name: 'Antoni', age: 10}
  ];
 
  var by = function (property) { //#3
    return function (a, b) {
      if (a[property] < b[property]) {
        return -1;
      } else if (a[property] === b[property]) {
        return 0;
      } else {
        return 1;
      }
    }
  };
 
  users.sort(by('age')); //#4
 
  var usersConcat = users.map((function(property) { //#5
    return function(user) { 
      return user[property]; 
    };
  }('name'))).join(', ');
 
  var resultContainer = document.getElementsByClassName('result')[0]; //#6
 
  resultContainer.innerHTML = usersConcat; //#7
} (this.window, this.window.document));

Korzystam tutaj z czystego Javascript, bez użycia biblioteki jQuery.
Analizując powyższy kod widzimy:

  1. Samowykonującą się funkcję, która jest "odpalana" w momencie gdy strona się załaduje;
  2. Tablicę obiektów;
  3. Funkcję by(), która jako swój parametr przyjmuje nazwę własności obiektu. Jest to przykład "higher-order function" - funkcja zwraca funkcję i jako swoje parametry przyjmuje wartości zwracane przez funkcję zagnieżdżoną głębiej.
    W tym przypadku, funkcja zagnieżdżona porównuje wartosci atrybutów między kolejnymi obiektami i zwraca ich kolejność. Należy pamiętać, że parametrami funkcji zagnieżdżonej są wartości przyjmowane przez funkcję zagnieżdżoną. Takie podejście wprowadza nas na nowy poziom abstrakcji w pisaniu kodu. Podobne podejście można wykorzystać w innych językach programowania.
  4. Wywołujemy funkcję sort(), która jest natywną funkcją JS, a jej parametrem jest funkcja;
  5. Tworzymy ciąg z wartosciami własnosci name, jest to kolejny przykład na wykorzystanie funkcji wyższego rzędu. Funkcja map ma za zadanie pomapować obiekty wg zdefiniowanych reguł. Przyjmuje funkcję jako swój parametr. Funkcja ta, staje się funkcją zagnieżdżoną, która zwraca wybrane wartości własnosci. Map() jest swoistym substytutem funkcji forEach(), ale dzięki funkcji map() możemy uniknąć tworzenia nadmiarowych zmiennych.
    Na koniec za pomocą join() łączymy wartości z tablicy w jeden ciąg słów przedzielonych przecinkiem;
  6. Szukamy pierwszego elementu w drzewie DOM posiadającego klasę result;
  7. Na koniec, wstawiamy wcześniej przygotowany ciąg imion do wybranego elementu na stronie (posiadającego klasę result).

Kiedy korzystać z funkcji wyższego rzędu?

Bardzo często z funkcji wyższego rzędu można korzystać w miejscach, gdzie jest wymagany callback, np. w wywołaniach AJAX tworzonych za pomocą biblioteki jQuery.
Higher-order functions występują często przy okazji wykorzystania natywnych funkcji JS dla tablic, takich jak: map(), reduce() czy filter(). Funkcje te przyjmują inne funkcje jako swoje parametry.

Zalety wykorzystania funkcji wyższego rzędu

  • redukcja używanych zmiennych,
  • uproszczenie kodu,
  • tworzenie ładnych "jednolinijkowców" (#4 z listingu powyżej).

Podsumowanie

Tym wpisem chciałem przybliżyć tematykę tworzenia funkcji wyższego rzędu, które pozwalają na uproszczenie kodu i redukcję zmiennych, co pozytywnie wpływa na wydajność aplikacji webowych.
Zdaję sobie sprawę, że dla osoby nie mającej do tej pory styczności z tego typu tworami JS może zająć trochę czasu jego dobre zrozumienie, lecz warto dać temu szansę.

Warto zajrzeć w te źródła: źródło1, źródło2

  • Comandeer

    nie do końca wiem na czym polegać ma to „uniknięcie” tworzenia dodatkowych zmiennych przy map zamiast forEach – w jednym i drugim przypadku tworzymy przecież jedną tablicę z wynikami
    no i ten przykład z map to nieźle przekombinowany 😉 gdyby to nie była dosłowna funkcja, to da się to zrozumieć
    i jeszcze jedno: co to oznacza this.window? przecież w globalnym scope this===window, zatem this.window to tak naprawdę window.window. masz szczęście, że window.window===window 😉

  • Nie zawsze trzeba się poruszać w globalnej przestrzeni nazw.
    Lecz wtym konkretnym przypadku, masz rację.