Funkcje anonimowe w JS

JS jako jeden z najbardziej ekspresyjnych języków posiada ciekawy i często wygodny mechanizm funkcji anonimowych.

Polega to na tworzeniu funkcji, które nie mają nazwy i są jakby przypisane do konkretnego miejsca w kodzie.

Spójrzmy na przykład (wykorzystanie zwykłych funkcji):

function example1 () {
    alert("example 1");
}

function example2 () {
    // tu cos robimy
    return example1();
}

example2();

Załóżmy teraz, że funkcja `example1′ jest wywoływana tylko w jednym miejscu, co więcej jej funkcjonalność jest ściśle związana z `example2′.

W tym wypadku jest jednak możliwość zrobienia sobie psikusa…

// kod z powyzszego listingu

// do zmiennej example1 przypisujemy
// referencje na inna funkcje!
example1 = parseInt; 

example2();

I co taki kod zrobi? Zwróci `NaN‘.

Dlaczego? Bo `example1’ wskazywało przy drugim wywołaniu na funkcję `parseInt‘, która przyjmuje jeden parametr i próbuje sparsować go do wartości liczby całkowitej. Skoro `example1’  otrzymywało ciąg “example1”, to nie da się z tego zrobić liczby. Stąd wartość Not a Number – NaN.

Jak to poprawić?

Funkcje wewnętrzne

W Javascript nie istnieją (w czasie pisania artykułu w roku 2011 – przyp. autora) przestrzenie nazw, a przynajmniej nie na zasadzie słowa kluczowego “namespace”. Można jednak osiągnąć bardzo podobny efekt za pomocą wykorzystania mechanizmu domknięć. Więc przeczytasz tu:

Możemy zatem zrobić coś takiego:

function example2()
{
    function example1()
    {
        alert("example 1");
    }
    // tu cos robimy
    return example1();
}

example2();

W ten sposób nie ma możliwości nadpisania funkcji `example1′, możemy być zatem pewnie wyniku działania `example2′. Chyba, że ktoś nam nadpisze `example1′ 😉

Gdybyśmy jednak chcieli uprościć zapis, możemy to zrobić w sposób pokazany poniżej.

Funkcje anonimowe

function example2()
{
    // tu cos robimy
    return function() { //2
        alert("example 1"); // 3
    }
}

example2(); // 1

Cóż to spowoduje? Nic. Dlaczego?! (Nic, to nie jest najlepsze określenie. Coś się stanie, jednak nie zobaczysz niczego).

Przypatrzmy się, co tu się dzieje:

  1. wywołujemy funkcję example2()
  2. funkcja ta zwraca funkcję. Nie wynik działania funkcji, a funkcję. Czyli, jak zrobimy tak:

Operator wywołania funkcji `()’

Z pewnością znasz wiele operatorów, choćby operator przypisania `=’, porównania `==’ lub `===’ (w JS istnieją dwa operatory porównujące). Także `()’ jest operatorem. Powoduje on próbę wykonania pewnego kodu, na który wskazuje zmienna.

function example2()
{
    // tu cos robimy
    return function() { //2
        alert("example 1"); // 3
    }
}

var e = example2(); // 1
e();
e();
alert(e); // wyswietli kod

to uzyskamy dwukrotnie alert “example 1” oraz raz zobaczymy kod funkcji anonimowej. Co zrobiliśmy? Przypisaliśmy do zmiennej `e’ referencję na funkcję (teraz ta zmienna ma w sobie wartość adresu, gdzie ta funkcja się znajduje w pamięci. Jeśli wywołasz funkcję – przez dodanie `()’ za nazwą zmiennej `e’ – zostanie wywołana funkcja przypisana pod zmienną `e’).

W poprzednim przypadku (tym z funkcją jawnie zdefiniowaną i nazwaną example1), moglibyśmy to otrzymać tak:

function example2()
{
    // tu cos robimy
    return example1; // zwracamy referencję na tę funkcję, nie wynik jej działania
}

Innymi słowy operator `()’ “uaktywnia” funkcję.

Aby od razu działało jak przykład 1:

function example2()
{
    return (function() { // 1
        alert("example 1");
    })(); // 2
}
example2();
  1. całe ciało anonimowej funkcji objęliśmy w nawiasy.
  2. dodaliśmy za ciałem funkcji operator wywołania `()’.

Wynik powyższego kodu będzie identyczny do działania pierwszego listingu z tego artykułu.
To oczywiście nie wszystko na temat funkcji anonimowych. Ale o innych rzeczach (rekurencja, sprytne triki) napiszę kiedy indziej 🙂

Nazwana funkcja anonimowa

Funkcja anonimowa może mieć swoją nazwę! No to zaraz, ktoś powie: “Co to za anonimowa?”. No może mieć nazwę, ale widoczną tylko dla niej samej:

function example2()
{
    return (function nazwaFunkcjiAnonimowej() { // 1
        alert(nazwaFunkcjiAnonimowej);
    })(); // 2
}
example2();

I po co? I co to daje? No można np. za pomocą funkcji anonimowej korzystać z rekurencji. Oto przykład najpopularniejszej rekurencji na świecie: silnia.

function policzSilnieInterfejs()
{
    var MAX = 8,
        n;

    do
    {
        n = parseInt(prompt("Podaj liczbę < " + MAX + ">"));
    } while(n < 0 || n > MAX || isNaN(n));

    return (function factorial(n) {
        return (0 === n) ? 1 : (n * factorial(n - 1));
    })(n);
}
alert(policzSilnieInterfejs());

Można byłoby wykorzystać tu także obiekt `arguments’ i jego właściwość `callee’, ale  tym w osobnym wpisie:

  • obiekt arguments – TODO

Do zrozumienia powyższego kodu przydatne mogą się okazać artykuły:

Warto pobawić się samemu. Wujek google też z pewnością pomoże 🙂

5 komentarzy

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

This site uses Akismet to reduce spam. Learn how your comment data is processed.