Nauka JavaScript: function( ) vs. ( ) = > Czym się różnią?

Uczysz się JavaScript i poszukujesz dodatkowych materiałów? W tym artykule znajdziesz informacje o tym czym różni się klasyczny sposób deklarowania funkcji za pomocą function( ) od składni ( ) = > wprowadzonej w standardzie ES6. Brzmi ciekawie?

Czego się dzisiaj dowiesz?

Dzisiejszy wpis mówi o różnicach przy deklaracji funkcji w standardowy sposób tzn. przy użyciu słowa kluczowego function ( ) a deklaracji funkcji strzałkowej tzn. ( ) = >. Dużo informacji i prostych przykładów. Łatwe w zrozumieniu.

Jeśli chcesz przejść do podsumowania gdzie wymieniam bezpośrednie porównanie kliknij TUTAJ.

Seria JavaScript

Blog Bedekodzic.pl traktuje o nauce programowania aplikacji webowych. Jak można o tym pisać bez znajomości JavaScript? No właśnie… Zakładając, że masz już podstawową wiedzę na temat JS’a będę objaśniał zagadnienia związane ze jego współczesnymi wersjami czyli ES6 i w górę!

Wszystkie wpisy z serii można śledzić na moim blogu pod tym LINKIEM

O co chodzi z funkcjami strzałkowymi?

Funkcje strzałkowe deklarowane za pomocą składni ( ) = > pojawiły się w JavaScripcie wraz z nadejściem ES6. Wcześniej funkcjonował tylko jeden sposób deklarowanie funkcji za pomocą składni function( ). Nowa metoda wprowadziła kilka udogodnień/zmian o których powiem w dalszej części wpisu.

Deklarowanie funkcji

Zanim przejdę do bardziej złożonych przykładów skupmy się na samym sposobie deklaracji różnych rodzajów funkcji.

Funkcja Anonimowa

Anonymous function. Czyli taka funkcja która nie posiada nazwy. Najpierw spróbuje ją zdeklarować a potem wywołać.

Function

function () {
    console.log('Bedekodzic.pl');   //ERROR - Unexpected token (
};

W ten sposób nie działa… To może spróbuje z nawiasami?

(function () {
    console.log('Bedekodzic.pl');
});

Teraz konsola nie wyrzuca mi błędu i funkcja jest zdeklarowana jednak… Jak ją wywołać skoro nie ma nazwy? Trzeba to zrobić za pomocą IIFE tzn. Immediately Invoked Function Expression. Czyli wywołać funkcję odrazu przy deklaracji. Jak to zrobić?

(function () {
    console.log('Bedekodzic.pl'); // “Bedekodzic.pl”
}());

Na końcu deklaracji należy dodać nawiasy a funkcja wykona się odrazu.

( ) =>

Teraz czas na funkcję strzałkową w tym wypadku deklaracja wygląda tak:

(() =>{
    console.log('Bedekodzic.pl 2');
}) ();

Wywołanie funkcji tak jak powyżej daje nam oczekiwany rezultat w konsoli.

Jeśli chcemy wywołać funkcje anonimowe przy użyciu “function” oraz “()=>” należy użyć metody IIFE.

Nadawanie funkcjom nazw

Kolejny przypadek czyli nadamy funkcjom nazwy. Odpowiednio regular i arrow.

Function

// Deklaracja
function regular () {
    console.log('Bedekodzic.pl');
}
// Wywołanie
regular();

Inny sposób:

// Deklaracja
let regular = function () {
    console.log('Bedekodzic.pl');
};
// Wywołanie
regular();

Obydwa przypadki działają poprawnie.

( ) =>

Teraz deklaracja funkcji strzałkowej:

// Deklaracja
let arrow = () =>{
    console.log('Bedekodzic.pl 2');
};
// Wywołanie
arrow();

Powyższy sposób działa bez zarzutów. Kolejny przypadek:

arrow2 () => {
    console.log('Bedekodzic.pl 2'); // ERROR - Unexpected token )
};
arrow2();

Uuu funkcja nie działa! Czyli żeby nadać funkcji strzałkowej nazwę należy przyporządkować ją do zmiennej! Pierwsza poważna różnica!

Aby nadać funkcji strzałkowej nazwę należy przyporządkować ją do zmiennej podczas gdy przy składni “function” nie jest to konieczne.

Funkcja czy zmienna?

To skoro przyporządkujemy funkcje do zmiennych to jak będą one traktowane przez JavaScript? Jako funkcje czy jako zmienne? Sprawdźmy…

Function

let regular = function () {};

// Sprawdzamy typ deklaracji
console.log(typeof regular); // function

// Sprawdzamy czy deklaracja jest funkcją
console.log(regular instanceof Function); // true

Obydwa sprawdzenia dają nam oczekiwany rezultat czyli mamy do czynienia z funkcją.

( ) =>

let arrow = () =>{};

// Sprawdzamy typ deklaracji
console.log(typeof arrow); // function

// Sprawdzamy czy deklaracja jest funkcją
console.log(arrow instanceof Function); // true

Rezultat jest ten sam jak w przypadku powyżej 🙂

Zarówno funkcje strzałkowe jak i klasyczne przyporządkowane do zmiennych są traktowane przez JS jako funkcje.

Funkcje z argumentami

Teraz do funkcji będę dodawał argumenty. Jak ktoś jeszcze nie rozumie całej koncepcji argumentów w funkcjach to szybki przykład.

Argumenty funkcji

Argument “a” jest dowolną wartością którą “wstrzykniemy” do funkcji a ta będzie wykonywała na niej jakieś operacje. Przykład:

let val;

let multi = function(a) {
    return val=a*2;
};

//Wywołanie funkcji z argumentem a=3
console.log(multi(3)); //6
console.log(val); //6

Powyżej zdefiniowaliśmy funkcję multi która będzie mnożyła dany argument przez 2. Potem pojawia się słowo kluczone return. O co w nim chodzi? Mówi ono co funkcja ma zwrócić poza swój zakres. W tym wypadku zmienną val. Jeśli chcesz poczytać więcej na temat zmiennych i zakresu funkcji sprawdź mój poprzedni WPIS

Teraz bardziej namacalny przykład tzn. funkcja zmodyfikuje właściwości CSS dowolnego elementu który przypiszemy do jakiejś zmiennej:

let el = document.getElementById('element');

let cssMod = function (object) {
    object.style.width = '100px';
}

cssMod(el);

Powyżej przypisujemy dowolny element do zmiennej “el” a następnie wywołujemy funkcję “cssMod” która domyślnie zmodyfikuje każdy element DOM przypisany jako argument 🙂 Tak właśnie działa magia argumentów funkcji 🙂 Wracamy do głównego wątku…

Function

Najpierw przypiszemy jeden argument funkcji:

let regular = function (a) {
    return a/2;
};

console.log(regular(10));

Teraz dwa argumenty:

let regular = function (a,b) {
    return a*b;
};

console.log(regular(5,4));

( ) =>

Jeden argument:

let arrow = a => a/2;

console.log(arrow(10));

Dwa argumenty:

let arrow2 = (a, b) => a+b;

console.log(arrow2(1,1));

lub tak, wychodzi na to samo:

let arrow2 = (a, b) => {
    return a+b;
};

console.log(arrow2(1,1));

Jak widać użycie funkcji strzałkowej znacząco zmniejsza ilość kodu który musimy napisać.

Deklaracja funkcji strzałkowej używającej argumentów może zmniejszyć ilość linijek kodu potrzebnych do napisania w porównaniu do funkcji klasycznej.

Dowiązywanie argumentów

Następnym aspektem pod którym przeanalizujemy obydwa przypadki to dowiązywanie parametrów czyli arguments binding. O co chodzi?

Po prostu na początku definiujemy funkcję bez podawania argumentów a następnie mimo wszystko wrzucamy do funkcji jakiś argument. Przykład:

Function

let regular = function(){
    console.log(arguments[0]); // ten kod pokaże w konsoli pierwszy z argumentów funkcji
};
regular('bedekodzic.pl'); // wywołujemy funkcję podając jej argument

Powyższa formuła zadziałała więc można dowiązać dowolny argument do funkcji.

( ) =>

Spróbujmy zrobić analogiczną operację na funkcji strzałkowej:

let arrow = () => {
    console.log(arguments[0]); // ERROR: arguments is not defined
};

arrow('bedekodzic.pl');

Niestety tym razem nie możemy dowiązać argumentu…

Funkcja strzałkowa może operować tylko na z góry zdefiniowanych argumentach. Nie można dowiązywać dodatkowych argumentów tak jak w funkcji klasycznej.

Dziedziczenie dowiązanych argumentów

To skoro nie możemy dowiązać argumentów do funkcji strzałkowej to sprawdźmy jak wygląda sprawa z dziedziczeniem argumentów tzn. mamy funkcję nadrzędną o podanych argumentach do tego funkcja podrzędna zawarta w jej zakresie. Teraz pytanie- czy funkcja podrzędna może korzystać z argumentów funkcji nadrzędnej?

Przeanalizujemy dwa przypadki tzn. klasyczna i strzałkowa funkcja podrzędna. Dla urozmaicenia do funkcji wrzucimy dwa argumenty.

Function

let regular = function () { // funkcja nadrzędna
  return function () { // funkcja podrzędna która analizuje dziedziczone argumenty
    console.log(arguments[0],arguments[1]);
  };
};

let check = regular('bedekodzic.pl','dobry blog'); // funkcja pomocnicza w której wstrzykujemy argumenty do funkcji nadrzędnej

check(); // wywołanie funkcji pomocniczej.

W tym wypadku wywołanie funkcji pomocniczej dało nam wynik undefined undefined. Znaczy to nie więcej niż to, że funkcja podrzędna nie była w stanie użyć argumentów funkcji nadrzędnej. Dziedziczenie nie działa…

( ) =>

Ten sam przykład przy użyciu funkcji strzałkowej:

let regular2 = function () { // funkcja nadrzędna
  return () => console.log(arguments[0],arguments[1]); // funkcja podrzędna
};

let check2 = regular2('bedekodzic.pl','dobry blog'); // funkcja pomocnicza

check2();

Teraz konsola wyrzuciła wartości ‘bedekodzic.pl’ ‚dobry blog’ czyli dwa podane argumenty. Dziedziczenie zadziałało i udało się użyć argumentów.

Funkcja strzałkowa jest w stanie dziedziczyć argumenty funkcji nadrzędnej.

Funkcja jako konstruktor

Kolejną kwestią będzie użycie funkcji jako konstruktora. O co chodzi z konstruktorem? Jest to funkcja która nadaje strukturę danych na podstawie której można stworzyć np. obiekt. Zrozumienie tej koncepcji nie jest oczywiste i zachęcam do zapoznania się z tym zagadnieniem. Pokażę prosty przykład który może być dobrym wstępem do nauki.

Function

let First = function(word1){ // funkcja będąca konstruktorem
  this.firstPar = word1; // określamy strukturę danych
};

let phrase = new First('Bedekodzic to'); // tworzymy nowy obiekt

console.log(phrase.firstPar); // wyświetlamy właściwość obiektu - 'Bedekodzic to’

Co się dzieje powyżej? Najpierw deklarujemy funkcję First które jest konstruktorem czyli przechowuje strukturę obiektu. Wewnątrz używamy kontekstu this.firstPar który w tym wypadku znaczy stwórz właściwość firstPar dla tego konstruktora a jej wartość ma być równa argumentowi word1.

Potem tworzymy obiekt phrase za pomocą słowa kluczowego new gdzie definiujemy argument dla konstruktora First.

Na końcu wywołujemy właściwość firstPar dla obiektu phrase który ma wartość argumentu word1.

Opisany kod działa jak należy czyli klasyczna funkcja może być użyta jako konstruktor.

( ) =>

Teraz powtórzę przykład za pomocą funkcji strzałkowej.

let First2 = (word1) => {
    this.word1 = word1;
};

let phrase2 = new First2('Bedekodzic to'); // ERROR First2 is not constructor

console.log(phrase2.word1);

Tym razem wyskoczył błąd. Nie można użyć funkcji strzałkowej jako konstruktora…

Funkcji strzałkowej nie można użyć jako konstruktora.

Tworzenie prototypów

Następnym krokiem będą prototypy. Czym są? Są to oddzielne funkcje lub nazwijmy to metody które mogą operować na obiekcie stworzonym za pomocą konstruktora. Po prostu wykonujemy funkcję przy użyciu obiektu.

W przykładzie będę próbował dodać do konstruktora wyraz który wraz z właściwością obiektu utworzy napis. Zrobię to przy użyciu prototypu.

Function

let First = function(word1){
    this.firstPar = word1;
};

let phrase = new First('Bedekodzic to '); // Tworzymy konstruktor i obiekt

First.prototype.second = function(word2) { // tworzę prototyp posiadający argument
  return this.firstPar + word2; // funkcja doda nowy argument do kontekstu konstruktora
};

console.log(phrase.second('fajny blog!')); // Wywołuje prototyp - ‘Bedekodzic to fajny blog!’

Powyższy kod działa. Czyli wykonuję pewną funkcję second będącą prototypem obiektu phrase stworzonej za pomocą konstruktora First. W praktyce dodaje do siebie 2 stringi – ‚Bedekodzic to ‘ i ‚fajny blog!’.

( ) =>

Spróbuje zrobić to samo za pomocą funkcji strzałkowej.

let First = function(word1){
    this.firstPar = word1;
};

let phrase = new First('Bedekodzic to ');

First.prototype.second2 = (word2) => {
    return this.firstPar + word2;
};

console.log(phrase.second2('fajny blog!')); // “undefined to fajny blog!”

Hmmm teraz otrzymałem string “undefined to fajny blog!”. Wygląda na to, że funkcja strzałkowa nie była w stanie odczytać kontekstu konstruktora… Więc funkcję strzałkowe nie nadają się do tworzenia prototypów!

Funkcje strzałkowe nie nadają się do tworzenia prototypów.

Dziedziczenie kontekstu

Jest jeszcze jedna sprawa którą chciałbym naświetlić. Wiemy już, że zarówno konstruktor jak i prototyp powinny być stworzone za pomocą funkcji klasycznej. Co jednak jeśli prototyp jest bardziej złożony i posiada funkcje podrzędne? Czy możemy dostać się do kontekstu konstruktora i wykonywać nasze operację?

W przykładach poniżej spróbuje wstawić do prototypu funkcje podrzędne które będą dodawały do siebie element konstruktora i argument prototypu tworząc string.

Function

let First = function (word1) {
    this.firstPar = word1;
};

let phrase = new First('Bedekodzic to ');

First.prototype.second = function (word2) {
    console.log(this.firstPar); // sprawdzam czy mam dostęp do kontekstu konstruktora - TAK
    return function() {
        console.log(this.firstPar); // sprawdzam czy mam dostęp do kontekstu konstruktora - NIE
        return this.firstPar + word2;
        
    } ();
};

console.log(phrase.second(['fajny blog!'])); // wynik to ‘undefinedfajny blog!’

Ten sposób nie działa. Funkcja klasyczna jako podrzędna nie może dostać się do kontekstu konstruktora… Są metody aby to obejść np. that=this czy bind(this) ale jest to tylko dodatkowa komplikacja.

( ) =>

let First = function (word1) {
    this.firstPar = word1;
};

let phrase = new First('Bedekodzic to ');

First.prototype.second2 = function (word2) {
    console.log(this.firstPar); // sprawdzam czy mam dostęp do kontekstu konstruktora - TAK
    return (() => {
        console.log(this.firstPar); // sprawdzam czy mam dostęp do kontekstu konstruktora - TAK
        return this.firstPar + word2;
        
    })();
};

console.log(phrase.second2(['fajny blog!'])); // wynik to ‘Bedekodzic to fajny blog!’

HAHA! Funkcja strzałkowa odczytała kontekst konstruktora! Wszystko działa jak należy.

Funkcja strzałkowa respektuje kontekst zdefiniowany w obrębie funkcji nadrzędnej i jest w stanie się do niego odwołać.

Na szczęście ES6 rozwiązuje wiele kwestii związanych z konstruktorem i prototypami poprzez wprowadzenie klas. Wszystkie powyższe rozważania można załatwić w ten sposób:

class FirstClass { // definiujemy klasę
  constructor(word1) { // definiujemy konstruktor
    this.firstPar = word1;
  }
  
  second(word2) { // definiujemy metodę
    return this.firstPar + word2;
  }
}

console.log(new FirstClass('Bedekodzic to ').second('fajny blog!')); // wywołujemy klasę i metodę

Prawda, że klasy wyglądają dużo prościej?

function( ) vs ( )=> – podsumowanie

Wyniki naszej analizy w szybkich punktach:

  • Jeśli chcemy wywołać funkcje anonimowe przy użyciu “function()” oraz “()=>” należy użyć metody IIFE.
  • Aby nadać funkcji strzałkowej nazwę należy przyporządkować ją do zmiennej podczas gdy przy składni “function()“ nie jest to konieczne.
  • Zarówno funkcje strzałkowe jak i klasyczne przyporządkowane do zmiennych są traktowane przez JS jako funkcje.
  • Deklaracja funkcji strzałkowej używającej argumentów może zmniejszyć ilość linijek kodu potrzebnych do napisania w porównaniu do funkcji klasycznej.
  • Funkcja strzałkowa może operować tylko na z góry zdefiniowanych argumentach. Nie można dowiązywać dodatkowych argumentów tak jak w funkcji klasycznej.
  • Funkcja strzałkowa jest w stanie dziedziczyć argumenty funkcji nadrzędnej.
  • Funkcji strzałkowej nie można użyć jako konstruktora.
  • Funkcję strzałkowe nie nadają się do tworzenia prototypów.
  • Funkcja strzałkowa respektuje kontekst zdefiniowany w obrębie funkcji nadrzędnej i jest w stanie się do niego odwołać.

Podsumowanie wpisu

Dzisiaj poruszyliśmy wiele aspektów związanych z JavaScriptem a to tylko przy okazji porównania dwóch rodzajów deklaracji funkcji w ES6. To pokazuje jak ważne jest to zagadnienie.

Jeśli o czymś zapomniałem lub tłumaczenia/przykłady podane we wpisie mogłyby być doprecyzowane napisz o tym proszę w komentarzu 🙂


Bartek Cis

Piszę dla was tego bloga bo lubię aplikacje internetowe. Mogę je projektować, kodować a potem o nich pisać czując dreszczyk ekscytacji za każdym razem gdy trafię na coś nowego. Bo uczymy się całe życie. Prawda?
  • Nieznaną liczbę argumentów można do obu rodzajów funkcji przekazać przez „rest operator”, czyli trzy kropki:
    const fn = (…args) => console.log(args[0], args[0])

    Zabrakło mi też słowa „leksykalny” w opisie funkcji strzałkowych. Mając one leksykalny this i leksykalne arguments.

    Ogółem: bardzo fajny wpis!