Funkcje strzałkowe w JavaScript

Cześć! To jest fragment książki JavaScript od podstaw, która ma pomóc w nauce programowania od zera.

Osławione już funkcje strzałkowe (ang. arrow functions) to krótszy zapis dla funkcji anonimowych, czyli funkcji bez nazwy. W innych językach programowania znane są najczęściej jako funkcje lambda, ale przyjęło się określać je tak a nie inaczej, gdyż nazwa pochodzi od charakterystycznej strzałki => w ich definicji. Aby pokazać, w jaki sposób je definiujemy, zacznijmy od kilku przykładów klasycznych funkcji anonimowych.

const call = function () {
  console.log("Called");
};

const print = function (obj) {
  console.log(obj);
};

const triple = function (value) {
  return value + value + value;
};

const add = function (a, b) {
  return a + b;
};

call(); // Called
print("AAA"); // AAA
console.log(triple(1)); // 3
console.log(triple("B")); // BBB
console.log(add("A", "B")); // AB

Aby zamienić klasyczne funkcje anonimowe na funkcje strzałkowe, wystarczy, że pozbędziemy się słowa function, a następnie umieścimy => pomiędzy nawiasem zamykającym parametry a tym określającym ciało funkcji. Poniżej przedstawione są wcześniejsze przykłady, ale zapisane już za pomocą nowej notacji.

const call = () => {
  console.log("Called");
};

const print = (obj) => {
  console.log(obj);
};

const triple = (value) => {
  return value + value + value;
};

const add = (a, b) => {
  return a + b;
};

call(); // Called
print("AAA"); // AAA
console.log(triple(1)); // 3
console.log(triple("B")); // BBB
console.log(add("A", "B")); // AB

Chociaż nowy zapis wprowadził na razie niewielkie zmiany, to funkcje strzałkowe mają o wiele więcej właściwości, które pozwalają skrócić ich zapis. Po pierwsze w sytuacji, gdy mamy do czynienia z dokładnie jednym parametrem (co w praktyce zdarza się dość często), nawias otaczający ten parametr jest opcjonalny, a tym samym możemy go pominąć.

const call = () => {
  console.log("Called");
};

const print = obj => { // Tutaj zniknął nawias
  console.log(obj);
};

const triple = value => { // Tutaj zniknął nawias
  return value + value + value;
};

const add = (a, b) => {
  return a + b;
};

call(); // Called
print("AAA"); // AAA
console.log(triple(1)); // 3
console.log(triple("B")); // BBB
console.log(add("A", "B")); // AB

Po drugie w sytuacji, gdy w ciele funkcji znajduje się tylko jedno wyrażenie1, możemy pominąć nawiasy klamrowe otaczające ciało. Nie używamy także słowa return, a mimo to wynik tego wyrażenia i tak zostanie zwrócony.

const call = () => console.log("Called");
// Zniknął nawias klamrowy

const print = obj => console.log(obj);
// Zniknął nawias klamrowy

const triple = value => value + value + value;
// Zniknął nawias oraz return

const add = (a, b) => a + b;
// Zniknął nawias oraz return

call(); // Called
print("AAA"); // AAA
console.log(triple(1)); // 3
console.log(triple("B")); // BBB
console.log(add("A", "B")); // AB

Zauważ jak bardzo powyższa metoda skraca i upraszcza nasze funkcje. Właściwie z elementów składowych pozostała sama strzałka =>. Teraz już rozumiesz, dlaczego przyjęło się mówić o nich "funkcje strzałkowe"? Skrótowość stanowi o ich popularności, gdyż są one najczęściej wykorzystywane tam, gdzie definiujemy krótkie funkcje. Dzięki użyciu tej notacji minimalizujemy zbędny kod i pomagamy czytelnikowi skupić się na tym, co ważne.

Przydatność funkcji strzałkowych

Cofnijmy się do poprzedniego rozdziału i przypomnijmy sobie, jak tworzyliśmy funkcje, które przekazywaliśmy dalej do forEach czy map. Były one dość długie i wymagały pewnych powtarzalnych elementów składniowych.

const shoppingList = ["Jabłko", "Banan", "Śliwka"];
const numbers = [1, 2, 3, 4];

let text = "";
shoppingList.forEach(function(item) {
  text += item + ", ";
});
console.log(text); // Jabłko, Banan, Śliwka,

const upper = shoppingList.map(function(item) {
  return item.toUpperCase();
});
console.log(upper); // [ 'JABŁKO', 'BANAN', 'ŚLIWKA' ]

const numberSquares = numbers.map(function(item) {
  return item * item;
});
console.log(numberSquares); // [ 1, 4, 9, 16 ]

Elementy takie jak function czy return zmuszają nas do nadmiarowego pisania i czytania kodu. Głównie dla takich przypadków powstały funkcje strzałkowe. Zauważ o ile skróci się nasz kod po ich użyciu.

const shoppingList = ["Jabłko", "Banan", "Śliwka"];
const numbers = [1, 2, 3, 4];

let text = "";
shoppingList.forEach(item => text += item + ", ");
console.log(text); // Jabłko, Banan, Śliwka,

const upper = shoppingList
  .map(item => item.toUpperCase());
console.log(upper); // [ 'JABŁKO', 'BANAN', 'ŚLIWKA' ]

const numberSquares = numbers.map(num => num * num);
console.log(numberSquares); // [ 1, 4, 9, 16 ]

Krótszy zapis to nie tylko wygoda. Niepotrzebny i powtarzalny kod utrudnia czytanie tego, co naprawdę istotne. Przypomina szum, który rozprasza i zmniejsza czytelność kodu. Dlatego przy krótkich funkcjach anonimowych, programiści preferują stosowanie funkcji strzałkowych.

Ograniczenia funkcji strzałkowych

Funkcje strzałkowe są współcześnie bardzo popularne i powszechnie stosowane. Warto jednak pamiętać o tym, że mają pewne ograniczenia względem klasycznych funkcji2:

  • nie mogą być używane jako konstruktory (nie zadziała słówko new);
  • nie są bindowane do this ani super, a więc nie powinny być używane jako metody, nie są też odpowiednie do wołania przez call, apply czy używania metody bind;
  • nie mają wsparcia dla słów kluczowych yield, arguments ani new.target.

Ćwiczenie: Funkcje strzałkowe przy przetwarzaniu kolekcji

W poniższej definicji zamień funkcje anonimowe na funkcje strzałkowe. Jakiego wyniku się spodziewasz? Czy jesteśmy w stanie zmniejszyć liczbę użytych funkcji?

const students = [
  {name: "Marian", score: 4.51, points: 19},
  {name: "Ania", score: 4.23, points: 21},
  {name: "Ala", score: 3.21, points: 21},
  {name: "Katarzyna", score: 3.77, points: 32},
  {name: "Józef", score: 4.21, points: 22},
  {name: "Rafał", score: 3.43, points: 23},
];

const list = students
  .filter(function (s) {
    return s.score >= 3.5;
  })
  .filter(function (s) {
    return s.points >= 20;
  })
  .map(function (s) {
    return s.name + ", " + s.score;
  })
  .forEach(function (str) {
    console.log(str);
  });

Odpowiedzi na końcu książki.

Ćwiczenie: Funkcje strzałkowe zamiast metod

Zamień poniższe funkcje na funkcje strzałkowe:

function times(a, b) {
  return a * b;
}

function compareScoresDescending(s1, s2) {
  return s2.score - s1.score;
}

function compareNames(s1, s2) {
  if(s1.name< s2.name) return -1;
  if(s1.name >s2.name) return 1;
  return 0;
}

Odpowiedzi na końcu książki.

Ćwiczenie: Z funkcji strzałkowych do anonimowych i nazwanych

Zamień poniższe funkcje strzałkowe na:

  1. funkcje anonimowe,
  2. funkcje nazwane.
const triple = i => i * 3;

const first = arr => arr[0];

const bigger = (a, b) => a > b ? a : b;

Odpowiedzi na końcu książki.

Współczesne środowiska programistyczne (narzędzia, w których zawodowi programiści piszą kod) oferują automatyczną zamianę funkcji anonimowej lub nazwanej na funkcję strzałkową, lub na odwrót.

Zakończenie

Przeszliśmy razem już naprawdę długą drogę. Zaczęliśmy od wyświetlania "Witaj, świecie" w konsoli, a teraz zobacz — przetwarzamy kolekcje przy użyciu funkcji strzałkowych niczym profesjonalni programiści. Tak, mamy jeszcze mnóstwo do nauczenia się, ale warto też podkreślić, że bardzo dużo już umiemy. Moje gratulacje! Zauważ jakie robisz postępy. Już niedługo porozmawiamy o tym, w jaki sposób możesz kontynuować swoją edukację, ale teraz czas utrwalić zdobytą wiedzę — nie zapominając przy tym o zabawie.

1:

Patrz Słowniczek na samym końcu książki.

2:

Pojęcia super, call, apply, bind, yield, arguments oraz new.target nie zostały wprowadzone i nie będą używane w dalszej części książki. Zostały uwzględnione jedynie by zawrzeć wszystkie istotne różnice.