Jason. Cała informatyka w jednym miejscu!

W końcu przechodzimy do funkcji! Nie chcę się powtarzać, natomiast jak się możecie domyślać, one także są podstawą do porządnego programowania. Funkcja w języku JavaScript jaka zostanie omówiona to nie tylko podprogram, ale także typ danych!!! Uwaga! Ten artykuł będzie ZNACZNIE większy od średniej wielkości pozostałych artykułów o języku JavaScript, gdyż zawiera w sobie szczegóły dotyczące późniejszych materiałów. Aby nie rozmieniać się na grosze, zdecydowałem już umieścić całą treść, a jeśli wyda się Wam ona za trudna, zawsze możecie ją pominąć :).

FUNKCJA W JĘZYKU JAVASCRIPT I WSZYSTKO O NIEJ CO MUSISZ WIEDZIEĆ!

"Let's begin"! Zaczniemy od terminologii. Mogło się Wam wiele razy obić o uszy słowo "funkcja" albo "metoda" (choć jest różnica pomiędzy tymi słowami). Gdybyście łapali się za głowę co to jest lub też jak to zdefiniować, a wiecie jak to wygląda, to podpowiem. Funkcja to podprogram mogący zwrócić wartość określonego typu danych, który ma prawo dysponować opcjonalnymi parametrami (fachowo "parametrami formalnymi", ale nie komplikujmy). Istnieją dwa najważniejsze powody tworzenia funkcji:

  1. pozwalają na wielokrotne wykonanie tej samej serii instrukcji przy tylko jednym zdefiniowaniu
  2. pozwalają segregować i separować od siebie instrukcje, aby "trzymały się" jednego tematu np. osobno rysowanie gracza i osobno sterowanie nim

Rozumiecie więc, że żeby Wasz kod spełniał minimalne wymagania, Waszym obowiązkiem jest zrozumienie zasad definiowania funkcji.

BUDOWA FUNKCJI

Najprostsza na świecie postać funkcji w JavaScript wygląda następująco:

function doSomething() {
	// instrukcje
}

Słowo kluczowe "function" oznacza definicję funkcji w języku JavaScript. Od niego zawsze zaczynacie, tak jak to było z pętlą "while" i instrukcją warunkową. Następnie wprowadzacie nazwę, która będzie de facto identyfikatorem dla późniejszych wywołań funkcji (o tym poniżej). Potem nawiasy okrągłe i ewentualne parametry (dwa nagłówki dalej), klamerki dla bloku instrukcji i wewnątrz nich wprowadzacie co chcecie. Zmienne, pętle, "if'y", komentarze i całą resztę.

To było streszczenie wszystkich podstawowych informacji, a teraz pochylimy się nad każdym z aspektów i go sobie rozwiniemy. A swoją drogą, teraz już wiecie czemu "console.log" i "alert" wymagają użycia nawiasów okrągłych :D.

WYWOŁYWANIE FUNKCJI

Mając definicję funkcji, nie oznacza to że jakakolwiek instrukcja zostanie zrobiona natychmiast po uruchomieniu programu. Funkcja w języku JavaScript jak to funkcja w każdym innym języku, musi zostać uruchomiona do działania. "Używanie" funkcji nazywane jest elegancko "wywoływaniem". Zatem, kiedy tworzymy funkcję, to ją definiujemy, a jej wykorzystywanie gdzie indziej w kodzie oznacza jej wywołanie. Warto też dodać, że definicja funkcji musi być "pierwsza" zanim dojdzie do wywołania. Innymi słowy, definicja musi "stać wyżej" w wierszach kodu. To wynika z działania interpretera i jego przetwarzania kodu schodząc z góry na dół.

Patrząc na przykład definicji funkcji ukazany powyżej, tak wyglądałoby jej wywołanie:

doSomething();

Podajemy już WYŁĄCZNIE SAMĄ NAZWĘ razem z nawiasami okrągłymi. Nawet kiedy funkcja nie przyjmuje żadnych parametrów, i tak je wstawiamy! To jest oznaczenie dla interpretera, że tę instrukcję ma traktować jak funkcję, a nie jak zmienną! Zapomnimy o nawiasach to nic się nie wykona, gdyż zostawimy samą referencję do funkcji czyli adres (więcej w dalszej części).

PARAMETRY FUNKCJI

Często potrzebna Wam będzie informacja o jakiejś wartości zmiennej, którą trzeba pobrać z innego fragmentu kodu, bo na przykład została zdefiniowana w innej funkcji albo jaka prywatna dana składowa klasy (o klasach będzie dużo później). Tak czy owak, możecie nie mieć możliwości "wyjęcia na wierzch" zmiennej, a jest potrzebna do operacji w funkcji. Właśnie temu służą parametry formalne. Są to zmienne lokalne występujące tylko w obrębie funkcji, do której należą. Znajdują się wewnątrz nawiasów okrągłych i to gruntownie odpowiada na pytanie po co się je pisze. Podczas wywoływania funkcji możemy wprowadzić potrzebne wartości pomiędzy nawiasy okrągłe i wtedy zostaną one przeniesione czy też wstawione w poszczególne "sloty".

Szybki przykład. Załóżmy, że mamy taką funkcję przyjmującą jeden parametr:

function writeText(text) {
	console.log(text);    
}

i wywołanie, które może być następujące:

writeText("Komunikat.");

To wówczas łańcuch znaków "Komunikat." zostanie przeniesiony do miejsca parametru o nazwie "text" i wykorzystany jako parametr wywoływanej funkcji "console.log".

Jeśli lubicie zagrywki encyklopedyczne, to możecie już teraz opanować rozróżnianie parametrów formalnych (parametry wewnątrz definicji funkcji) od parametrów aktualnych (parametry wstawiane do wywołania) nazywanych powszechnie "argumentami". To jest tylko dodatkowa terminologia, na którą nie musicie nawet patrzeć.

WAŻNE!

Zanim przejdziecie dalej, małe uświadomienie na ile możecie sobie pozwolić. Ktoś mógłby zadać pytanie: "A co jeżeli liczba podanych parametrów nie będzie zgodna z liczbą parametrów oczekiwanych?". To jest dobre pytanie. Efekt uboczny zależy od tego, czy jest ich za dużo, czy za mało:

  • jeżeli podanych parametrów jest za mało, to wszystkie te, które nie otrzymały wartości, będą typu "undefined"
  • jeżeli podanych parametrów jest za dużo, to wszystkie "nadmiarowe" zostaną zignorowane

Zwrócę jeszcze uwagę na fakt, że choć to będzie mieć nieprzyjemne konsekwencje, to wcale nie oznacza to pożegnania się z uruchomieniem programu! Program może się zawalić w trakcie jego wykonywania! Ma to związek z działaniem interpretera, który wykonuje instrukcje jedna po drugiej "na żywo". To nie jest kompilator, którego zadaniem jest analiza całego kodu źródłowego i zwrócenie nowego kodu wynikowego, jeżeli oczywiście żadne instrukcje nie łamią zasad składni. Wtedy wszystko musi być "tip top"! Język JavaScript mocno odszedł od powszechnej zasady zgodności typów jak również liczby parametrów i tak ma praktycznie każdy język skryptowy. W językach kompilowanych takich jak C# czy Java wszystko MUSI zazębiać się perfekcyjnie. O jeden parametr za mało, źle (chyba, że język wspiera wartości domyślne np. Python lub Kotlin). O jeden za dużo? Też źle. Nie zgadza się typ danych któregokolwiek z parametrów, żegnamy się z kompilacją. W JavaScript jest "luz blues" :). Jeśli coś jest nie tak jak powinno, nie zabroni nam uruchomić programu, ale "kwiatek" może wyskoczyć nagle podczas jego działania i tego musicie być świadomi! Późniejsze poszukiwanie problemu nie należy do przyjemnych zadań w dniu pracy programisty.

LOKALNY ZASIĘG ZMIENNYCH

Umieszczając wszelkie zmienne wewnątrz funkcji, musicie pamiętać o zasięgu widoczności. Klamerki określają niejako "granicę" istnienia wszystkich zamieszczonych tam danych. Zmienna nazwana przykładowo "x" zdefiniowana w bloku funkcji będzie występować tylko tam i nigdzie indziej! Dlatego też taki kod:

function doSomething() {
	let x = 5;
	
	console.log(x);
}

spowoduje prawidłowe wyświetlenie wartości zmiennej, ale kiedy przyjdzie nam do głowy odwołanie się do niej na zewnątrz:

function doSomething() {
	let x = 5;
	
	console.log(x);
}

x = 80;

musimy pogodzić się z porażką i wyświetleniem niezdefiniowanego typu danych.

ZWROT WARTOŚCI PRZEZ FUNKCJĘ

Ktoś z Was zna już jakiś inny język co najmniej do funkcji? To zapewne wie, że istnieje coś takiego jak implementacja zwracania wartości danego typu przez funkcję. Nie inaczej jest tutaj. Istotnie, używając słowa kluczowego "return", jesteśmy w stanie sprawić żeby dana funkcja zwróciła coś na wyjściu wykorzystując podstawianie do zmiennej czy parametru innej funkcji. Jeszcze ważniejszą wiadomością jest fakt, iż tutaj nie podajemy żadnych "void", "int" i wielu innych typów danych jakie to trzeba określać w nagłówku funkcji w większości innych języków wysokiego poziomu. W języku JavaScript, "function" to zawsze jest "function", bez względu na to czy coś zwraca, czy nie. Pozwólcie na kolejny przykład:

function square(n) {
	return n**2;
}

Właśnie użyłem słowa "return" do zwrócenia kwadratu podanej liczby. "return" natychmiast kończy dalsze egzekwowanie funkcji, więc możecie go użyć nawet dla funkcji, które nic nie zwracają. Po prostu dajecie samo słowo kluczowe ze średnikiem i koniec:

function doSomething() {
	console.log("To się wykona.");
	return;
	console.log("To się nie wykona.");
}

KONWENCJA NAZEWNICZA

Wtrącenie odnośnie nazewnictwa. Obowiązuje wszystko to, co dotyczy zmiennych "let" i stałych "const". Co do zaleceń, to najlepiej żeby nazwa funkcji "mówiła" co ona robi (np. "fire") albo co ma zwracać na wyjściu (np. "gainedPoints" albo w formie czasownika "getGainedPoints"). Ale to tylko sugestia, która stała się powszechną normą w dzisiejszym programowaniu.

ZMIENNA MOŻE WSKAZYWAĆ NA FUNKCJĘ!

Zadziwię Was do tego stopnia, że funkcję można bez żadnych problemów podstawić do zmiennej, czyli dosłownie "przypisać funkcję", o tak:

const square = function(n) {return n**2;};

Taka funkcja określana jest jako funkcja "anonimowa", gdyż ona sama nie posiada nazwy. To fakt, że zmienna posiada własną etykietę, ale ona jedynie wskazuje na tę funkcję. Ale...dlaczego to JEST dopuszczalne? Przecież w wielu innych językach zostałoby to odebrane jako działanie człowieka niespełna rozumu! Tutaj jest całkowicie poprawne od strony składniowej! Dlaczego? Wynika to z dwóch rzeczy.

Po pierwsze, funkcja w języku JavaScript to oprócz słowa kluczowego, osobny typ danych! Możecie nie pamiętać tego, że wspomniałem o tym w artykule o typach danych. Nie bez kozery. To jest również typ danych. Jak poddacie powyższą stałą małej próbie wywołując "console.log" i korzystając z "typeof":

console.log(typeof square);

to zobaczycie, że nie ściemniam. Nie ma również przeciwwskazań do przypisywania definicji funkcji przebywających niezależnie. Innymi słowy, podstawianie definicji funkcji w postaci ukazanej w sekcji "Zwrot wartości przez funkcję", jest jak najbardziej możliwe:

function square(n) {
	return n**2;
}

const sqr = square;

console.log(sqr(5));

A po drugie, jest to dopuszczalne ponieważ funkcja przechowuje w rzeczywistości adres w systemie szesnastkowym, czyli referencję w pamięci! Gdyby zajrzeć głęboko do języka C, to tam znaleźlibyście manewr polegający na przypisaniu nazwy funkcji do wskaźnika. Tak się robi w celu zaprogramowania "wywołania zwrotnego", czyli mechanizmu wywołania określonej funkcji pod wpływem jakiegoś zdarzenia np. wyjście z aplikacji. W języku C# także znajdziecie ten sam mechanizm, tylko różniący się innym hasełkiem, "delegate". Nie chcę wchodzić w szczegóły, bo to zbyt daleko odbiega od sprawy. W każdym razie przyjmijcie do wiadomości, że kiedy podstawiacie funkcję pod zmienną, to przechowuje ona nie samą jej definicję, tylko adres w pamięci i dzięki niemu może tę funkcję odnaleźć i wywołać.

FUNKCJA JAKO LAMBDA

Mamy jeszcze jedno tajemne wcielenie funkcji! Funkcja w języku JavaScript może być też przekazana do innej funkcji...jako parametr!!! Określa się to z kolei "wyrażeniem lambda" i chodzi tutaj o przekazywanie jako parametru samego kodu zamiast wartości! Otwiera to drogę do wykonywania różnych instrukcji co każde wywołanie funkcji przyjmującej za parametr lambdę. Do tego się to sprowadza pisząc prostymi słowy, a szczegóły zostały uwiecznione w odrębnym wpisie. Za przykład można podać dzień roboczy pracownika. Jednego dnia może segregować papiery, innego dnia aktualizować krotki w bazie danych, a jeszcze następnego poprosić szefa o dzień wolny. Sprowadzając to do postaci funkcji:

function work(action) {
	console.log("Mój dzień pracy");
	action();
	console.log("Koniec dnia pracy");
}

uzyskujemy w ten sposób definicję z jednym parametrem potrzebującym dosłownie innej funkcji jako lambdy. A przykładowe wywołania:

function sortPapers() {
	console.log("Sortuję papiery.");
}

function updateRecordsInDatabase() {
	console.log("Aktualizuję rekordy w bazie danych.");
}

function askBossForFreeDay() {
	console.log("Robię sobie dzień wolny.");
}

work(sortPapers);
work(updateRecordsInDatabase);
work(askBossForFreeDay);

pozwalają na określanie ZUPEŁNIE RÓŻNYCH bloków kodu jakie wykonają się wewnątrz innej funkcji. Dozwolony jest również taki manewr:

work(function() {
    console.log("Projektuję stronę internetową.");
});

To jest bezpośrednie osadzenie ciała funkcji (lambdy) do parametru. Wtedy taka funkcja istnieje tylko dla tego wywołania, gdyż nie jest nigdzie przypisana do niej referencja.

FUNKCJA NALEŻĄCA DO OBIEKTU

Znacie strukturę obiektów? Mam dobrą wiadomość dla miłośników programowania obiektowego. Funkcja może się znaleźć w obiekcie, tym samym symulując metody klasy z języków czysto obiektowych, takich jak wszystkim znana Java:

const player = {
	health: 100,
	attack: function() {
		console.log("Gracz atakuje!");
	}
};

A nawiasem pisząc, wtedy określa się to metodą, a nie funkcją! Tak samo jak w Javie czy "CSharpie"!

FUNKCJA ZWRACAJĄCA OBIEKT

Dysponując dotychczas otrzymanymi informacjami, możemy zastosować składniowy trik polegający na połączeniu słowa "return" z klamerkami obiektu:

function createPlayer() {
	return {
		health: 100,
		attack: function() {
			console.log("Gracz atakuje!");
		}
	};
}

Uwaga, klamerka otwierająca MUSI znaleźć się w tej samej linijce, bo inaczej błąd składniowy! Wtedy korzystając z takiej instrukcji:

const player = createPlayer();

tworzymy obiekt gracza! Ale uwaga! Jest dostępny jeszcze jeden wariant :D!

FUNKCJA-KONSTRUKTOR

Ostatnia postać funkcji jaką chcę zaprezentować to funkcja jako konstruktor obiektu. Są takie sytuacje, w których chcielibyśmy aby funkcja w języku JavaScript robiła za "fabryczkę" obiektów danego typu. Wtedy już nie korzystamy "z otoczki" w postaci klamerek, tylko piszemy wszystko już w samym ciele funkcji:

function Player() {
	this.health = 100;
	this.attack = function() {
		console.log("Gracz atakuje!");
	}
}

"this" w dużym skrócie oznacza odniesienie się do samego siebie, więc instancja powstała w wyniku działania "funkcji-konstruktora", będzie posiadała właściwość "health" i metodę "attack". Co do samego tworzenia obiektu, wykorzystujemy słowo kluczowe "new" do utworzenia instancji:

const player = new Player();

"new" jest obowiązkowe, bo inaczej zostanie przypisany typ "undefined" i nie dojdzie do prawidłowego utworzenia obiektu. Wielka litera w nazwie funkcji jest wstawiona specjalnie, gdyż oznacza ona "semi klasę". Przyjmuje się też, że w takiej sytuacji nazwa powinna odzwierciedlać typ klasy (zwróć uwagę na różnicę pomiędzy "createPlayer", a "Player"!).

Jest także szansa skorzystania z klasy "prawdziwej" przy użyciu słowa kluczowego "class" i tworzenia obiektów na jej podstawie! Odpuścimy sobie tutaj przykład, aby nie mieszać dwóch wątków naraz.

Funkcja w języku JavaScript

Słowo kluczowe "function" posiada wiele twarzy. Jest typem danych, definiuje funkcje, a one same mogą przybierać kilka różnych postaci!


Oto prawdziwy koniec artykułu. Przytoczyłem chyba wszystkie transformacje jakie mogą przyjąć funkcja w języku JavaScript :).

PODOBNE ARTYKUŁY