Funkcje i zakres – tworzenie i używanie funkcji, zrozumienie zakresu. Część 2

10 lutego, 2024

Wprowadzenie do funkcji w JavaScript

Funkcje i zakres - tworzenie i używanie funkcji, zrozumienie zakresu. Część 2

Wprowadzenie do funkcji w JavaScript

Funkcje są podstawowym budulcem każdego programu JavaScript. Pozwalają one na kapsułkowanie kodu w celu jego wielokrotnego użytku, organizacji oraz łatwiejszego zarządzania. Dzięki funkcjom, można podzielić złożone zadania na mniejsze, bardziej zarządzalne fragmenty, co znacznie ułatwia proces programowania i testowania. W tej sekcji przyjrzymy się, czym są funkcje i dlaczego odgrywają one tak ważną rolę w języku JavaScript.

Czym są funkcje?

Funkcja to blok kodu zaprojektowany do wykonywania określonego zadania. Gdy funkcja jest wywołana, wykonuje ona swój kod. Funkcje mogą przyjmować parametry, które wpływają na ich działanie, oraz mogą zwracać wartości. Funkcje w JavaScript są obiektami, co oznacza, że można je przypisać do zmiennych, przekazywać jako argumenty do innych funkcji oraz posiadać swoje metody i właściwości.

Dlaczego funkcje są ważne w JavaScript?

  1. Modularyzacja: Funkcje pozwalają na podział kodu na mniejsze, bardziej zarządzalne bloki, co sprawia, że programy są łatwiejsze w utrzymaniu i rozumieniu.
  2. Wielokrotne użycie: Raz napisaną funkcję można wielokrotnie wywoływać z różnymi argumentami, co zmniejsza redundancję kodu.
  3. Zakres: Funkcje pomagają w zarządzaniu zakresem zmiennych, co jest kluczowe dla zachowania czystości i przewidywalności kodu.
  4. Zamykania: Funkcje w JavaScript mogą tworzyć zamykania, co oznacza, że mają dostęp do zmiennych zewnętrznych w swoim zakresie, nawet po zakończeniu wykonania kodu, który je utworzył.

Funkcje są nieodłączną częścią JavaScript i zrozumienie ich działania, składni oraz zastosowań jest niezbędne dla każdego programisty. W kolejnych sekcjach zagłębimy się w różne aspekty funkcji, zaczynając od ich tworzenia, przez omówienie zakresu zmiennych, aż po bardziej zaawansowane koncepcje, takie jak zamykania i wzorce projektowe związane z funkcjami.

Tworzenie funkcji

Tworzenie funkcji w JavaScript jest procesem, który daje programistom ogromną elastyczność. Istnieje kilka sposobów na zdefiniowanie funkcji, każdy z nich ma swoje zastosowanie w zależności od kontekstu i potrzeb. W tej sekcji omówimy podstawowe sposoby tworzenia funkcji: deklaracje funkcji, wyrażenia funkcyjne oraz funkcje strzałkowe (arrow functions).

Składnia funkcji

Podstawowa składnia funkcji w JavaScript jest stosunkowo prosta i składa się z kilku kluczowych elementów.

Deklaracja funkcji

Deklaracja funkcji jest jednym z najbardziej rozpowszechnionych sposobów tworzenia funkcji. Ma ona następującą strukturę:

function nazwaFunkcji(parametr1, parametr2) {   // kod do wykonania
}
  • nazwaFunkcji: Nazwa, za pomocą której możemy odwoływać się do funkcji.
  • parametr1, parametr2: Parametry, które funkcja przyjmuje. Funkcja może przyjmować dowolną liczbę parametrów, lub nie przyjmować żadnych.
  • kod do wykonania: Kod wewnątrz funkcji, który jest wykonywany, gdy funkcja jest wywoływana.
Wyrażenie funkcyjne

Wyrażenie funkcyjne to inny sposób definiowania funkcji, polegający na przypisaniu funkcji do zmiennej. Funkcje zdefiniowane w ten sposób nie mają własnych nazw – są anonimowe.

const nazwaFunkcji = function(parametr1, parametr2) {
// kod do wykonania
};

Różnica między deklaracją funkcji a wyrażeniem funkcyjnym polega głównie na hoistingu. Deklaracje funkcji są hoistowane (przenoszone na początek zakresu), co oznacza, że funkcja może być wywołana przed jej deklaracją w kodzie. Wyrażenia funkcyjne tego nie umożliwiają.

Funkcje strzałkowe (Arrow functions)

Funkcje strzałkowe, wprowadzone w ES6, oferują zwięzłą składnię i są szczególnie użyteczne w przypadku funkcji anonimowych. Nie tworzą one własnego kontekstu this, co oznacza, że this wewnątrz funkcji strzałkowej odnosi się do wartości this z zewnętrznego zakresu.

const nazwaFunkcji = (parametr1, parametr2) => { // kod do wykonania };
Jeśli funkcja strzałkowa przyjmuje tylko jeden argument, można pominąć nawiasy. Ponadto, jeśli ciało funkcji zawiera tylko jedną instrukcję, która zwraca wartość, można pominąć klamry {} i słowo kluczowe return.
const funkcjaJednoargumentowa = parametr => parametr + 1;

Zrozumienie różnych sposobów tworzenia funkcji pozwala na elastyczne i efektywne programowanie w JavaScript. Każdy sposób ma swoje zastosowanie i najlepiej jest zrozumieć, kiedy i dlaczego używać każdego z nich.

Funkcje i zakres - tworzenie i używanie funkcji, zrozumienie zakresu. Część 2

3. Zakres zmiennych (Scope)

Zakres zmiennych, znany również jako „scope”, to jedno z fundamentalnych pojęć w JavaScript, które określa dostępność zmiennych w różnych częściach kodu. Rozumienie zakresu jest niezbędne, aby efektywnie zarządzać zmiennymi i unikać błędów. W tej sekcji omówimy różne rodzaje zakresów w JavaScript: globalny, lokalny i blokowy.

Co to jest zakres zmiennych?

Zakres zmiennych to kontekst w kodzie, w którym zmienna jest dostępna. JavaScript używa leksykalnego zakresu (lexical scoping), co oznacza, że dostępność zmiennej jest określona przez jej położenie w kodzie podczas pisania programu.

Zakres globalny vs. zakres lokalny

  • Zakres globalny: Zmienne zadeklarowane poza jakąkolwiek funkcją lub blokiem kodu są w zakresie globalnym. Są dostępne i modyfikowalne z dowolnego miejsca w programie. Nadmierny użytek zmiennych globalnych jest jednak uważany za złą praktykę, ponieważ zwiększa ryzyko konfliktów nazw i utrudnia zarządzanie kodem.

    var zmiennaGlobalna = "Dostępna wszędzie";
  • Zakres lokalny (funkcji): Zmienne zadeklarowane wewnątrz funkcji są w zakresie lokalnym tej funkcji. Oznacza to, że są dostępne tylko wewnątrz tej funkcji i nie są widoczne poza nią.

    function przykladowaFunkcja() { var zmiennaLokalna = "Dostępna tylko w tej funkcji"; }

Zakres blokowy (let/const)

  • Zakres blokowy: JavaScript (od ES6) wprowadził słowa kluczowe let i const, które pozwalają na deklarowanie zmiennych z zakresem blokowym. Oznacza to, że zmienna jest dostępna tylko wewnątrz bloku kodu (np. wewnątrz pętli lub instrukcji warunkowej), w którym została zadeklarowana.

    if (true) { let zmiennaBlokowa = "Dostępna tylko w tym bloku"; } 

Efekt hoistingu

Hoisting (wynoszenie) to zachowanie JavaScript, polegające na przenoszeniu deklaracji zmiennych (z var) i funkcji na początek zakresu przed ich fizycznym wystąpieniem w kodzie. Ważne jest, aby zrozumieć, że tylko deklaracje są hoistowane, nie inicjalizacje.

Rozumienie zakresu zmiennych jest kluczowe dla pisania czytelnego, efektywnego i bezbłędnego kodu w JavaScript. Pomaga to unikać problemów związanych z nadpisywaniem lub niezamierzonym dostępem do zmiennych.

Funkcje i zakres - tworzenie i używanie funkcji, zrozumienie zakresu. Część 2

Zrozumienie kontekstu this w funkcjach

W JavaScript, słowo kluczowe this odnosi się do obiektu, w kontekście którego została wywołana funkcja. Zrozumienie, jak this działa w różnych kontekstach, jest niezbędne, aby efektywnie korzystać z języka i unikać typowych pułapek związanych z jego dynamiką.

Jak this działa w JavaScript

Wartość this, w kontekście funkcji, jest dynamiczna i zależy od tego, jak i gdzie funkcja została wywołana. Oto kilka scenariuszy, które pokazują, jak this może się zmieniać:

  • W metodzie obiektu: this odnosi się do obiektu, na którym została wywołana metoda.

const obiekt = {
  metoda: function() {
    console.log(this); // odnosi się do obiektu
  }
};
obiekt.metoda();

W funkcji globalnej lub w trybie ścisłym (strict mode): W globalnej funkcji this odnosi się do globalnego obiektu (w przeglądarkach jest to window). Jednak w trybie ścisłym ('use strict'), this będzie undefined, jeśli funkcja nie została wywołana jako metoda obiektu.

function funkcjaGlobalna() {
  console.log(this); // 'this' odnosi się do globalnego obiektu lub jest 'undefined' w trybie ścisłym
}
funkcjaGlobalna();

W konstruktorze: Gdy używana jest funkcja konstruktora z operatorem new, this odnosi się do nowo utworzonego obiektu.

function Konstruktor() {
  this.wlasciwosc = 'wartość';
}
const instancja = new Konstruktor();
console.log(instancja.wlasciwosc); // 'wartość'

this w funkcjach tradycyjnych vs. funkcjach strzałkowych

Funkcje strzałkowe (arrow functions) wprowadziły istotną zmianę w sposobie traktowania this. W odróżnieniu od tradycyjnych funkcji, wartość this w funkcjach strzałkowych jest lexically scoped, co oznacza, że przyjmuje wartość z kontekstu, w którym funkcja została zdefiniowana, a nie z kontekstu, w którym została wywołana.

const obiekt = {
  metoda: () => {
    console.log(this); // 'this' odnosi się do kontekstu otaczającego, nie do obiektu
  }
};
obiekt.metoda(); // W przypadku funkcji strzałkowej, 'this' może nie wskazywać na obiekt

Zrozumienie różnic w zachowaniu this między różnymi typami funkcji jest kluczowe dla pisania przewidywalnego i efektywnego kodu w JavaScript. Pozwala to na odpowiednie zarządzanie kontekstem i unikanie błędów związanych z nieprawidłowym odniesieniem się do this.

Funkcje i zakres - tworzenie i używanie funkcji, zrozumienie zakresu. Część 2

Zamykania (Closures)

Zamykania (closures) to ważny i potężny koncept w JavaScript, pozwalający na tworzenie funkcji wraz z ich leksykalnym środowiskiem. Dzięki zamykaniom, funkcja może pamiętać i mieć dostęp do zmiennych z zakresu, w którym została zdefiniowana, nawet po zakończeniu wykonania tego zakresu. W tej sekcji przyjrzymy się, jak działają zamykania i jakie mają zastosowanie.

Co to są zamykania?

Zamykanie to funkcja wraz ze wszystkimi zmiennymi z jej leksykalnego zakresu, do których ma dostęp. Zamykania powstają naturalnie, gdy funkcja jest zdefiniowana w innym zakresie, a zmienna z tego zakresu jest używana wewnątrz funkcji.

function zewnetrznaFunkcja() {
  let zmiennaZewnetrzna = "Jestem zewnątrz!";

  function wewnetrznaFunkcja() {
    console.log(zmiennaZewnetrzna); // Dostęp do zmiennejZewnetrznej
  }
  return wewnetrznaFunkcja;
}

const mojeZamykanie = zewnetrznaFunkcja();
mojeZamykanie(); // Wypisze: "Jestem zewnątrz!"

W powyższym przykładzie, wewnetrznaFunkcja jest zamykaniem, które ma dostęp do zmiennej zmiennaZewnetrzna z funkcji zewnetrznaFunkcja.

Praktyczne zastosowanie zamykań

  1. Prywatność danych: Zamykania mogą być używane do tworzenia prywatnych zmiennych, które nie są dostępne bezpośrednio z zewnątrz.
  2. Kuratoryzowanie funkcji: Możesz użyć zamykań do tworzenia specjalizowanych wersji ogólnych funkcji.
  3. Funkcje z pamięcią: Zamykania pozwalają na tworzenie funkcji, które „pamiętają” pewne informacje między wywołaniami.

Wzorce projektowe związane z funkcjami

Zamykania to również podstawa dla wielu wzorców projektowych w JavaScript, takich jak moduły. Wzorce te pozwalają na lepszą organizację kodu, większą modularność i enkapsulację.

const modul = (function() {
  let prywatnaZmienna = "Jestem prywatna";
  
  function prywatnaFunkcja() {
    console.log(prywatnaZmienna);
  }

  return {
    publicznaFunkcja: function() {
      prywatnaFunkcja();
    }
  };
})();

modul.publicznaFunkcja(); // Wypisze: "Jestem prywatna"

W powyższym przykładzie, prywatnaZmienna i prywatnaFunkcja są prywatne i niedostępne bezpośrednio z zewnątrz, ale publicznaFunkcja dostępna jest publicznie i może być używana do interakcji z modułem.

Zrozumienie zamykań otwiera drzwi do głębszego zrozumienia JavaScript i pozwala na pisanie bardziej zaawansowanego, czystego oraz wydajnego kodu.

Wzorce projektowe związane z funkcjami

W JavaScript istnieje wiele wzorców projektowych, które pozwalają na lepszą organizację kodu, zapewniają enkapsulację i zachowują czystość struktury programu. W tej sekcji przyjrzymy się dwóm popularnym wzorcom: Natychmiastowo Wywoływanym Wyrażeniom Funkcyjnym (Immediately Invoked Function Expressions – IIFE) oraz konstrukcji modułów.

Natychmiastowo Wywoływane Wyrażenia Funkcyjne (IIFE)

IIFE to wzorzec, w którym funkcja jest definiowana i wywoływana natychmiast po jej utworzeniu. Ten wzorzec jest często używany do tworzenia nowego zakresu i unikania zanieczyszczenia globalnego zakresu.

(function() {
  var zmiennaPrywatna = "Nie jestem dostępna na zewnątrz";
  console.log(zmiennaPrywatna);
})(); // Wywołuje funkcję natychmiast po jej zdefiniowaniu

Dzięki IIFE, zmiennaPrywatna jest dostępna tylko wewnątrz funkcji i nie koliduje z innymi zmiennymi w globalnym zakresie.

Moduły i konstrukcje modularne

Wzorzec modułu pozwala na tworzenie enkapsulowanych części kodu, które ujawniają tylko wybrane części swojej funkcjonalności, zachowując pozostałą część prywatną. Moduły są fundamentalnym wzorcem w JavaScript, szczególnie w dużych aplikacjach, gdzie zarządzanie zależnościami i przestrzeniami nazw jest kluczowe.

var Modul = (function() {
  var prywatnaZmienna = "Jestem prywatna";
  
  function prywatnaFunkcja() {
    console.log(prywatnaZmienna);
  }

  return {
    publicznaFunkcja: function() {
      prywatnaFunkcja();
    }
  };
})();

Modul.publicznaFunkcja(); // Ma dostęp do prywatnej funkcji i zmiennej

W tym wzorcu, wszystko co jest zwracane w obiekcie return stanowi publiczny interfejs modułu, podczas gdy wszystko, co pozostaje wewnątrz funkcji anonimowej, jest prywatne.

Zrozumienie tych wzorców i umiejętne ich stosowanie może znacznie poprawić jakość Twojego kodu, pomagając w organizacji, utrzymaniu i skalowaniu aplikacji.

Dobre praktyki w tworzeniu funkcji

Pisanie funkcji w JavaScript to nie tylko kwestia składni i zrozumienia zakresu czy zamykań. Równie ważne jest przestrzeganie dobrych praktyk, które zapewniają, że kod jest czysty, zrozumiały i łatwy w utrzymaniu. W tej sekcji omówimy kilka kluczowych zasad, które warto stosować podczas tworzenia funkcji.

Czysty kod

Czysty kod to taki, który jest łatwy do zrozumienia i modyfikacji. Aby utrzymać czystość kodu w funkcjach, warto pamiętać o:

  • Jednoznacznych nazwach: Nazwy funkcji powinny jasno wskazywać, co funkcja robi. Używaj czasowników i unikaj nazw, które są niejasne lub zbyt ogólne.
  • Krótkich funkcjach: Dąż do tego, aby funkcje były krótkie i skupione na jednym zadaniu. Funkcja robiąca zbyt wiele komplikuje kod i utrudnia jego testowanie i debugowanie.
  • Ograniczonej liczbie parametrów: Im więcej parametrów ma funkcja, tym trudniejsza jest w użyciu i testowaniu. Jeśli funkcja wymaga wielu danych, rozważ użycie obiektu, aby przekazać parametry.

Zasada pojedynczej odpowiedzialności

Każda funkcja powinna być odpowiedzialna za jedną rzecz. Jeśli funkcja zajmuje się wieloma zadaniami, może to być znak, że należy ją podzielić na mniejsze, bardziej zarządzalne części.

Unikanie efektów ubocznych

Efekty uboczne to zmiany stanu aplikacji, które nie wynikają bezpośrednio z wykonania danej funkcji. Funkcje z efektami ubocznymi są trudniejsze do zrozumienia i testowania. Aby unikać efektów ubocznych:

  • Nie modyfikuj globalnych zmiennych: Używaj lokalnych zmiennych i przekazuj wszystkie potrzebne dane jako parametry.
  • Nie modyfikuj obiektów, które są przekazywane jako parametry: Zamiast tego zwróć nowy obiekt z żądanymi zmianami.

Przestrzeganie tych zasad może znacznie poprawić jakość Twojego kodu, sprawiając, że będzie on bardziej zrozumiały, łatwiejszy w utrzymaniu i mniej podatny na błędy.

Podsumowanie

W tym wpisie zagłębiliśmy się w świat funkcji JavaScript, odkrywając nie tylko składnię i różne sposoby definiowania funkcji, ale także zrozumieliśmy kluczowe koncepcje, takie jak zakres zmiennych, kontekst this, zamykania oraz wzorce projektowe. Ponadto, omówiliśmy dobre praktyki, które pomagają w utrzymaniu kodu czystego, zrozumiałego i łatwego w utrzymaniu.

Co powinieneś zabrać z tego wpisu:

  1. Zrozumienie funkcji: Zrozumiałeś różne sposoby tworzenia funkcji w JavaScript, w tym deklaracje funkcji, wyrażenia funkcyjne oraz funkcje strzałkowe.

  2. Zakres zmiennych: Zdobycie wiedzy na temat zakresu zmiennych pozwoliło Ci zrozumieć, jak zarządzać zmiennymi w Twoim kodzie, unikając konfliktów i błędów.

  3. Kontekst this: Poznałeś, jak this działa w różnych kontekstach, co jest kluczowe dla efektywnego wykorzystania funkcji i metod.

  4. Zamykania: Zrozumiałeś, jak zamykania pozwalają funkcjom pamiętać i mieć dostęp do zmiennych z ich leksykalnego zakresu, nawet po zakończeniu wykonania tego zakresu.

  5. Wzorce projektowe: Zapoznałeś się z natychmiastowo wywoływanymi wyrażeniami funkcyjnymi (IIFE) i modułami, co pozwala na lepszą organizację kodu i jego enkapsulację.

  6. Dobre praktyki: Nauczyłeś się, jak pisać funkcje, które są czyste, skoncentrowane na jednym zadaniu i unikają efektów ubocznych, co sprawia, że są łatwiejsze w utrzymaniu i testowaniu.

Pamiętaj, że praktyka jest kluczem do opanowania JavaScript. Eksperymentuj z kodem, stosuj nowo poznaną wiedzę w praktycznych projektach i nieustannie poszukuj sposobów na ulepszanie swoich umiejętności.