Funkcje w JavaScript są przedstawicielem złożonego typu danych. Pozwalają na wykonywanie procedur i operacji na danych. Wynikiem wykonania funkcji może być zwrócona przez nią wartość i/lub efekt uboczny.
JavaScript pozwala na tworzenie funkcji za pomocą różnych składni np.
Deklaracja funkcji:
javascript
function myFunctionName(functionParameter) {
if (typeof functionParameter !== "string") {
throw new TypeError("Function parameter must be a string");
}
console.log(`Hello ${functionParameter}!`);
return `Goodbye ${functionParameter}.`;
}
const valueFromFunction = myFunctionName("Tom");
// Hello Tom!
console.log(valueFromFunction);
// Goodbye Tom.
Wyrażenie funkcyjne:
javascript
const myFunctionName = function (functionParameter) {
if (typeof functionParameter !== "string") {
throw new TypeError("Function parameter must be a string");
}
console.log(`Hello ${functionParameter}!`);
return `Goodbye ${functionParameter}.`;
};
const valueFromFunction = myFunctionName("Tom");
// Hello Tom!
console.log(valueFromFunction);
// Goodbye Tom.
Funkcja strzałkowa:
javascript
const myFunctionName = (functionParameter) => {
if (typeof functionParameter !== "string") {
throw new TypeError("Function parameter must be a string");
}
console.log(`Hello ${functionParameter}!`);
return `Goodbye ${functionParameter}.`;
};
const valueFromFunction = myFunctionName("Tom");
// Hello Tom!
console.log(valueFromFunction);
// Goodbye Tom.
Składnia strzałkowa jest bardzo przydatna w przypadku krótkich funkcji. Dla nich skrócenie kodu jest najbardziej widoczne. W przypadku funkcji jednoliniowych zwracających zwracających wartość możliwe jest pominiecie zarówno nawiasów jak i słowa kluczowego return
.
javascript
const square = (number) => number ** 2;
const valueFromFunction = square(2);
console.log(valueFromFunction);
// 4
Korzystając z funkcji strzałkowych należy pamiętać, że nie tworzą one własnego kontekstu this
. W związku z tym nie mogą być wykorzystywane jako konstruktor nowego obiektu.
Składnia wyrażenia funkcyjnego wygląda bardzo podobnie do składni deklaracji funkcji z tą różnicą, że wyrażenie funkcyjne przypisuje funkcję anonimową (nie posiadająca nazwy) jako wartość zmiennej. Jednak zachowanie tych funkcji może się znacząco różnić. Ma to związek z tzw. windowaniem (ang. hoisting). Deklaracje funkcji w JavaScript są przenoszone na górę swojego zasięgu (ang. scope). Powoduje to, że mogą zostać wywołane zanim zostaną zadeklarowane. Stwierdzenie to nie odnosi się do funkcji korzystających ze składni wyrażenia funkcyjnego.
W związku z tym taki kod (korzystający z deklaracji funkcji) zadziała:
javascript
functionDeclaration();
// Hello there!
function functionDeclaration() {
console.log("Hello there!");
}
A bardzo podobny kod bazujący na wyrażeniu funkcyjnym wyrzuci wyjątek:
javascript
functionExpression();
// ReferenceError: Cannot access 'functionExpression' before initialization
const functionExpression = function () {
console.log("Hello there!");
};
Funkcje posiadające wiele parametrów wymagają podczas ich wywoływania przekazania równie dużej liczby argumentów. Dodatkowo konieczne jest zachowanie odpowiedniej ich kolejności.
javascript
const printPostInfo = function (title, publishedAt, author, description) {
console.log(`
Wpis: "${title}" - ${publishedAt}
Autor: "${author}"
---
${description}
`);
};
const title = "JavaScript - funkcje (Function).";
const publishedAt = "2021-01-23";
const author = "Łukasz Zieliński";
const description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
printPostInfo(title, publishedAt, author, description);
// Wpis: "JavaScript - funkcje (Function)." - 2021-01-23
// Autor: "Łukasz Zieliński"
// ---
// Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Takie funkcje nie są dobrą praktyką. Wpływają one negatywnie na czytelność kodu. Dodatkowo w języku dynamicznie typowanym takim jak JavaScript trudniej o narzędzia, które pozwoliłyby podczas wywoływania funkcji poznać oczekiwane przez nią parametry. Dlatego w przypadku funkcji z większą liczba parametrów warto opakować je w obiekt. Potrzebne parametry można odzyskać z obiektu poprzez destrukturyzacje. Pozwoli to rozwiązać problem z koniecznością podawania argumentów w odpowiedniej kolejności, a samo wywołanie funkcji będzie wyglądało czyściej.
javascript
const printPostInfo = function (post) {
const { title, publishedAt, author, description } = post;
console.log(`
Wpis: "${title}" - ${publishedAt}
Autor: "${author}"
---
${description}
`);
};
const title = "JavaScript - funkcje (Function).";
const publishedAt = "2021-01-23";
const author = "Łukasz Zieliński";
const description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
let post = { author, description, publishedAt, title };
printPostInfo(post);
// Wpis: "JavaScript - funkcje (Function)." - 2021-01-23
// Autor: "Łukasz Zieliński"
// ---
// Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Przy okazji omawiania funkcji w JavaScript warto poświęcić część uwagi terminowi czystej funkcji (ang. pure function). Wywodzi się on z programowania funkcyjnego i odnosi do funkcji, która:
javascript
let pureCountTotal = (itemPrices, shipping) => {
let sum = (first, second) => first + second;
return itemPrices.reduce(sum, shipping);
};
let itemPrices = [10, 5, 25];
const shipping = 5;
const total = pureCountTotal(itemPrices, shipping);
console.log(total);
// 45
Przedstawiona wyżej pureCountTotal
jest właśnie przykładem czystej funkcji. Zwracany przez nią rezultat zależy jedynie od wartości argumentów przekazanych podczas jej wywoływania. Dodatkowo podczas wywołania nie modyfikuje ona żadnych zmiennych spoza swojego zakresu, ani nie posiada żadnego efektu obocznego (drukowanie wartości w konsoli wykonywane jest poza samą funkcją). Jej przeciwieństwem jest funkcja przedstawiona poniżej:
javascript
let impureCountTotal = (itemPrices) => {
let sum = (first, second) => first + second;
total = itemPrices.reduce(sum, shipping);
console.log(`Total: ${total}`);
};
let total;
let itemPrices = [10, 5, 25];
const shipping = 5;
impureCountTotal(itemPrices);
// Total: 45
console.log(total);
// 45
W przypadku impureCountTotal
można zauważyć naruszenie wszystkich wcześniej odpisanych cech czystej funkcji:
shipping
.total
.shipping
.Jak widać dwa powyższe przykłady pomimo rozwiązywania tego samego problemu (obliczenia sumy cen produktów z przesyłką) znacząco się różnią. O ile w kontekście krótkiego fragmentu kodu nie ma to tak dużego znaczenia, to podczas pracy z bardziej złożoną aplikacją podejście funkcyjne pozwoli na uzyskanie czytelniejszego i łatwiejszego w testowaniu kodu. W rezultacie takie podejście może zmniejszyć ryzyko wprowadzania błędów do systemu podczas jego modyfikacji.
Podczas omawiania funkcji w JavasCript wato zwrócić uwagę na funkcje wyższego rzędu (ang. higher-order function), czyli takie, które są w stanie przyjmować/zwracać inne funkcje. Przykładami funkcji przyjmujących jako parametr inne funkcje mogą być dostępne dla obiektów dziedziczących po Array
funkcje: map
, filter
i reduce
. Przykładem funkcji zwracającej inną funkcję może być przedstawiona poniżej createSignature
, która przyjmuje imię i zwraca funkcję doklejającą wcześniej podane imię jako podpis.
javascript
const createSignature = function (name) {
return function (statement) {
return `${statement} - ${name}`;
};
};
const signAsJohn = createSignature("John");
const greeting = "Hello!";
const johnsGreeting = signAsJohn(greeting);
console.log(johnsGreeting);
// Hello! - John
Reasumując: funkcje w JavaScript są specjalnym typem obiektów. Pozwalają one na opakowywanie operacji na danych i procedur w re-używalne bloki. Istnieją rożne sposoby deklarowania funkcji, w zależności od wybranego sposobu jej zachowanie może się różnić. Podczas pracy z funkcjami warto przynajmniej w pewnym stopniu kierować się zasadami programowania funkcyjnego, gdyż potencjalnie mogą one wpłynąć pozytywnie na jakość kodu. Funkcję przyjmującą, lub zwracająca inna funkcję nazywa się funkcją wyższego rzędu.
Źródła: