Rozdział 2. Podstawy języka JavaScript

> Dodaj do ulubionych

Wprowadzenie

Podstawę biblioteki jQuery stanowi JavaScript, który sam w sobie jest niezwykle bogatym i ekspresyjnym językiem. W poniższej sekcji poznamy podstawowe pojęcia języka JavaScript, a także przyjrzymy się czekającym na nowicjuszy pułapkom. Omówione zagadnienia skierowane są przede wszystkim do początkujących, jednak zapoznanie się z charakterystycznymi cechami JavaScriptu może przynieść korzyści również programistom mającym doświadczenie w innych językach programowania.

Jeśli chcesz dowiedzieć się więcej na temat języka JavaScript, gorąco polecam książkę Douglasa Crockforda JavaScript: The Good Parts oraz nasz kurs programowania w języku JavaScript autorstwa Marijna Haverbeke

Podstawy składni JavaScriptu

Omówienie instrukcji, nazewnictwa zmiennych, białych znaków oraz innych podstawowych elementów składni języka JavaScript.

Przykład 2.1. Prosta deklaracja zmiennej

var foo = 'witaj, świecie';

Przykład 2.2. Poza cudzysłowem białe znaki nie pełnią żadnej funkcji

var foo = 'witaj, świecie';

Przykład 2.3. Nawiasy oznaczają pierwszeństwo wykonywania działania

2 * 3 + 5;    // zwraca 11; pierwsze wykonywane jest mnożenie
2 * (3 + 5);  // zwraca 16; pierwsze wykonywane jest dodawanie

Przykład 2.4. Tabulatory zwiększają czytelność kodu, lecz nie mają żadnego specjalnego znaczenia

var foo = function() {
    console.log('witaj');
};

Operatory JavaScript

Podstawowe operatory

Używając podstawowych operatorów możemy manipulować wartościami.

Przykład 2.5. Łączenie (konkatenacja)

var foo = 'witaj,';
var bar = 'świecie';

console.log(foo + ' ' + bar); // witaj, świecie

Przykład 2.6. Mnożenie i dzielenie

2 * 3;
2 / 3;

Przykład 2.7. Inkrementacja i dekrementacja

var i = 1;

var j = ++i;  // preinkrementacja:  j równa się 2; i równa się 2
var k = i++;  // postinkrementacja: k równa się 2; i równa się 3

Operacje na liczbach i ciągach znaków

Czasem liczby i ciągi w języku JavaScript mogą zachowywać się nie do końca zgodnie z naszymi przewidywaniami.

Przykład 2.8. Dodawanie vs. łączenie

var foo = 1;
var bar = '2';
console.log(foo + bar);  // 12, ups

Przykład 2.9. Jak sprawić, by ciąg stał się liczbą

var foo = 1;
var bar = '2';
 // wymuszamy, aby ciąg stał się liczbą
console.log(foo + Number(bar));

Wywołanie konstruktora Number jako funkcji (tak jak w przykładzie powyżej) sprawi, że jego argument będzie traktowany jako liczba. Ten sam rezultat osiągniemy używając jednoargumentowego operatora plus:

Przykład 2.10. Jak sprawić, by ciąg stał się liczbą (przy użyciu jednoargumentowego operatora plus)

console.log(foo + +bar);

Operatory logiczne

Operatory logiczne umożliwiają oszacowanie argumentów poprzez operacje I oraz LUB.

Przykład 2.11. Operatory logiczne I oraz LUB

var foo = 1;
var bar = 0;
var baz = 2;

foo || bar;   // zwraca 1, czyli prawdę
bar || foo;   // zwraca 1, czyli prawdę

foo && bar;   // zwraca 0, czyli fałsz
foo && baz;   // zwraca 2, czyli prawdę
baz && foo;   // zwraca 1, czyli prawdę

Być może przykład nie ilustruje działania operatorów zbyt jasno, zatem krótkie wyjaśnienie: operator || zwraca wartość pierwszego argumentu, który jest prawdziwy, zaś w przypadku, gdy obydwa argumenty są fałszywe, zwracany jest ostatni argument. Operator && zwraca wartość pierwszego fałszywego argumentu, lub wartość ostatniego, jeśli obydwa argumenty są prawdziwe.

Nie zapomnij zajrzeć do sekcji „Rzeczy prawdziwe, rzeczy fałszywe”, aby dowiedzieć się więcej na temat tego, które wartości są prawdziwe (true), a które fałszywe (false).

Operatory porównywania

Dzięki operatorom porównywania możemy sprawdzić, czy dane wartości są sobie równe bądź ze sobą identyczne.

Przykład 2.12. Operatory porównywania

var foo = 1;
var bar = 0;
var baz = '1';
var bim = 2;

foo == bar;   // zwraca fałsz
foo != bar;   // zwraca prawdę
foo == baz;   // zwraca prawdę; uwaga!

foo === baz;             // zwraca fałsz
foo !== baz;             // zwraca prawdę
foo === parseInt(baz);   // zwraca prawdę

foo > bim;    // zwraca fałsz
bim > baz;    // zwraca prawdę
foo <= baz;   // zwraca prawdę

Kod warunkowy

Czasami chcemy, aby blok kodu został wykonany tylko w określonej sytuacji. Umożliwia nam to kontrola przepływu sterowania za pomocą instrukcji if-else.

Przykład 2.13. Sterowanie wykonaniem programu

var foo = true;
var bar = false;

if (bar) {
    // ten kod nigdy nie zostanie wykonany
    console.log('witaj!');
}

if (bar) {
    // kod nie zostanie wykonany
} else {
    if (foo) {
        // kod zostanie wykonany
    } else {
        // kod zostanie wykonany jeśli zarówno foo i bar będą fałszywe
    }
}

Rzeczy prawdziwe, rzeczy fałszywe

Aby umiejętnie sterować działaniem programu, należy wiedzieć, które wartości są prawdziwe, a które fałszywe. Zdarza się, że wartości, które pozornie wydają się fałszywe bądź prawdziwe zwracają przeciwną wartość.

Przykład 2.14. Wartości, które zwracają prawdę

'0';
'dowolny ciąg';
[];  // pusta tablica
{};  // pusty obiekt
1;   // dowolna liczba różna od zera

Przykład 2.15. Wartości, które zwracają fałsz

0;
'';  // pusty ciąg
NaN; // zmienna, która nie jest typu liczbowego
null;
undefined;  // uwaga – undefined może mieć zmienioną definicję!

Przypisanie warunkowe zmiennych za pomocą operatora trójargumentowego

Czasami chcemy przypisać zmiennej daną wartość w zależności od tego, czy został spełniony określony warunek. Możemy wówczas użyć instrukcji warunkowej if-else, lecz w wielu przypadkach znacznie wygodniejszym rozwiązaniem jest operator trójargumentowy. [Definicja: operator trójargumentowy sprawdza, czy został spełniony określony warunek; jeśli tak, zwracana jest odpowiednia wartość, jeśli nie — inna.]

Przykład 2.16. Operator trójargumentowy

// jeśli bar jest prawdziwe, niech foo równa się 1
// w przeciwnym wypadku niech foo będzie równe 0
var foo = bar ? 1 : 0;

Operatora trójargumentowego można używać bez przypisania zmiennej wartość zwrotnej, jednak lepiej tego nie robić.

Instrukcja switch

Czasami zamiast kilku bloków if/else-if/else wygodniej jest użyć instrukcji switch. [Definicja: instrukcja switch sprawdza wartość danego wyrażenia bądź zmiennej i wykonuje poszczególne bloki kodu zależnie od tej wartości.]

Przykład 2.17. Instrukcja switch

switch (foo) {

    case 'bar':
        alert('wartość bar – ojej!');
    break;

    case 'baz':
        alert('buu baz :(');
    break;

    default:
        alert('wszystko pozostałe może być');
    break;
}

Instrukcja switch nie jest już zbyt chętnie wykorzystywana przez programistów JavaScript, ponieważ często do osiągnięcia tych samych rezultatów można wykorzystać obiekty. Są one wygodniejsze do ponownego użycia czy testów. Na przykład:

var coMamZrobic = {
    'bar' : function() {
        alert('wartość bar – ojej!');
    },

    'baz' : function() {
        alert('buu baz :(');
    },

    'default' : function() {
        alert('wszystko pozostałe może być');
    }
};

if (coMamZrobic[foo]) {
    coMamZrobic[foo]();
} else {
    coMamZrobic['default']();
}

Obiektom przyjrzymy się bliżej w dalszej części rozdziału.

Pętle języka JavaScript

Dzięki pętlom możemy wykonać blok kodu określoną liczbę razy.

Przykład 2.18. Pętle

// rejestruje 'próba 0', 'próba 1', ..., 'próba 4'
for (var i=0; i<5; i++) {
    console.log('próba ' + i);
}
Zwróć uwagę, że choć w pętlach używamy słowa kluczowego var przed nazwą zmiennej i, to jednak „zakres” zmiennej nie ogranicza się tylko do bloku pętli. Pojęcie zakresu zmiennych zostanie dokładniej omówione w dalszej części rozdziału.

Pętla for

Pętla for składa się z czterech instrukcji. Jej struktura wygląda następująco:

for ([inicjacja]; [warunek]; [iteracja])
 [trescPetli]

Instrukcja inicjująca wykonywana jest tylko raz, przed rozpoczęciem pętli. Wówczas możemy przygotować lub zadeklarować jakieś zmienne.

Instrukcja warunkowa wykonywana jest przed każdą iteracją, a od jej wartości zwrotnej zależy dalsze wykonywanie pętli. W przypadku wartości fałszywej pętla się zatrzymuje.

Instrukcja iteracja wykonywana jest na końcu każdej iteracji — wtedy też można zmienić stan istotnych zmiennych. Zazwyczaj zwiększa bądź zmniejsza się wówczas licznik, co przybliża pętlę do końca.

TrescFunkcji wykonywana jest w czasie każdej iteracji i może zawierać dowolny kod. Zazwyczaj treść funkcji składa się z wielu instrukcji, które muszą zostać wykonane i są otoczone klamrami ({...}).

Oto typowa pętla for:

Przykład 2.19. Typowa pętla for

for (var i = 0, limit = 100; i < limit; i++) {
    // Ten blok kodu zostanie wykonany 100 razy
    console.log(Iteracja nr ' + i);
    // Uwaga: jako ostatni wyświetli się komunikat "Iteracja nr 99"
}

Pętla while

Pętla while swoją budową przypomina instrukcję warunkową if, przy czym treść tej pętli jest wykonywana aż do momentu, w którym dany warunek nie jest już dłużej spełniany.

while ([warunek]) [trescPetli]

Oto typowa pętla while:

Przykład 2.20. Typowa pętla while

var i = 0;
while (i < 100) {

    // Ten blok kodu zostanie wykonany 100 razy
    console.log('Iteracja nr ' + i);

    i++; // inkrementacja i

}

Z pewnością zauważysz, że w treści pętli musimy dokonać inkrementacji licznika. Licznik można połączyć z warunkiem, tak jak zaprezentowano poniżej:

Przykład 2.21. Pętla while, w której licznik jest inkrementowany po sprawdzeniu na nim warunku

var i = -1;
while (++i < 100) {
    // Ten blok kodu zostanie wykonany 100 razy
    console.log('Iteracja nr ' + i);
}

Zwróć uwagę, że zaczynamy z poziomu -1 i korzystamy z przedrostkowego operatora inkrementacji (++i), czyli preinkrementacji.

Pętla do-while

Jest to praktycznie taka sama pętla co while — różnica polega na tym, że treść pętli do-while jest wykonywana co najmniej jeden raz, przed sprawdzeniem warunku.

do [trescPetli] while ([warunek])

Oto pętla do-while:

Przykład 2.22. Pętla do-while

do {

    // Nawet w przypadku niespełnienia warunku
    // treść pętli zostanie wykonana raz.

    alert('Hej!');

} while (false);

Pętle tego typu wykorzystywane są dość rzadko, ponieważ tylko w nielicznych wypadkach chcemy, aby pętla została wykonana bez sprawdzenia jakiegokolwiek warunku. Mimo wszystko o istnieniu pętli do-while warto wiedzieć.

Instrukcje break i continue

Zazwyczaj zatrzymanie pętli następuje po niespełnieniu określonego warunku, lecz można do tego również doprowadzić umieszczając w treści funkcji instrukcję break.

Przykład 2.23. Zatrzymywanie pętli

for (var i = 0; i < 10; i++) {
    if (coś) {
        break;
    }
}

Możemy również chcieć kontynuować pętlę bez wykonywania jej dalszej treści — służy do tego instrukcja continue.

Przykład 2.24. Przeskakujemy do następnej iteracji pętli

for (var i = 0; i < 10; i++) {

    if (coś) {
        continue;
    }

    // Poniższa instrukcja zostanie wykonana tylko wówczas,
    // jeśli warunek coś nie zostanie spełniony
    console.log('Koniec');

}

Słowa zarezerwowane

W języku JavaScript istnieje pewna liczba „słów zarezerwowanych” — są to słowa o specjalnym znaczeniu. Słów tych należy używać w kodzie tylko zgodnie z ich określonym znaczeniem.

  • abstract
  • boolean
  • break
  • byte
  • case
  • catch
  • char
  • class
  • const
  • continue
  • debugger
  • default
  • delete
  • do
  • double
  • else
  • enum
  • export
  • extends
  • final
  • finally
  • float
  • for
  • function
  • goto
  • if
  • implements
  • import
  • in
  • instanceof
  • int
  • interface
  • long
  • native
  • new
  • package
  • private
  • protected
  • public
  • return
  • short
  • static
  • super
  • switch
  • synchronized
  • this
  • throw
  • throws
  • transient
  • try
  • typeof
  • var
  • void
  • volatile
  • while
  • with

Tablice JavaScript

Tablice to indeksowane od zera listy wartości. Dzięki tablicom można w wygodny sposób przechowywać zbiór związanych ze sobą elementów tego samego typu (na przykład ciągów), choć w rzeczywistości w tablicy mogą znajdować się różne typy elementów, w tym inne tablice.

Przykład 2.25. Prosta tablica

var mojaTablica = [ 'witaj', 'świecie' ];

Przykład 2.26. Dostęp do tablicy poprzez indeks

var mojaTablica = [ 'witaj', 'świecie', 'foo', 'bar' ];
console.log(mojaTablica[3]);   // rejestruje 'bar'

Przykład 2.27. Sprawdzamy rozmiar tablicy

var mojaTablica = [ 'witaj', 'świecie' ];
console.log(mojaTablica.length);   // rejestruje 2

Przykład 2.28. Zmiana wartości elementu

var mojaTablica = [ 'witaj', 'świecie' ];
mojaTablica[1] = 'zmieniony';
Choć istnieje możliwość zmiany wartości tablicy tak jak zaprezentowano to w przykładzie 2.28, lepiej tego unikać.

Przykład 2.29. Dodawanie elementów do tablicy

var mojaTablica = [ 'witaj', 'świecie' ];
mojaTablica.push('nowy element');
Przykład 2.30: Korzystanie z tablic
var mojaTablica = [ 'w', 'i', 't', 'a', 'j' ];
var mojCiag = mojaTablica.join('');   // 'witaj'
var mojPodzial = mojCiag.split('');  // [ 'w', 'i', 't', 'a', 'j' ]

Obiekty JavaScript

Obiekty zawierają jedną lub więcej par klucz-wartość. Kluczem może być dowolny ciąg, zaś wartość może stanowić dowolny typ wartości: liczba, ciąg, tablica, funkcja, a nawet inny obiekt.

Definicja: Kiedy jedna z wartości jest funkcją, nazywa się ją metodą danego obiektu. W pozostałych przypadkach wartości nazywane są własnościami lub atrybutami.

Jak się okazuje, praktycznie wszystko w języku JavaScript jest obiektem — tablice, liczby, funkcje, a nawet ciągi. Każde z nich ma swoje własności i metody.

Przykład 2.31. Tworzymy „literał obiektowy”

var mojObiekt = {
    przywitajSie : function() {
        console.log('witaj');
    },

    mojeImie : 'Joanna'
};

mojObiekt.przywitajSie();            // rejestruje 'witaj'
console.log(mojObiekt.mojeImie);   // rejestruje 'Joanna'

Funkcje JavaScript

Funkcje służą do przechowywania bloków kodu, który ma być wykorzystany wielokrotnie. Mogą one przyjmować od zera do kilku argumentów, a także zwracać wartość.

Funkcje można tworzyć na różne sposoby:

Przykład 2.32. Definicja funkcji

function foo() { /* zrób coś */ }

Przykład 2.33. Nazwane wyrażenie funkcyjne

var foo = function() { /* zrób coś */ }
Osobiście wolę, z pewnych przyczyn technicznych, tworzyć funkcje poprzez wyrażenia funkcyjne. Generalnie można się jednak spotkać z obiema metodami.

Korzystanie z funkcji

Przykład 2.34. Prosta funkcja

var przywitaj = function(osoba, powitanie) {
    var tekst = powitanie + ', ' + osoba;
    console.log(tekst);
};
przywitaj('Rebecca', 'Witaj');
Przykład 2.35: Funkcja zwracająca wartość
var przywitaj = function(osoba, powitanie) {
    var tekst = powitanie + ', ' + osoba;
    return tekst;
};

console.log(przywitaj('Rebecca','witaj'));

Przykład 2.36. Funkcja zwracająca inną funkcję

var przywitaj = function(osoba, powitanie) {
    var tekst = powitanie + ', ' + osoba;
    return function() { console.log(tekst); };
};


var pozdrowienie = przywitaj('Rebecca', 'Witaj');
pozdrowienie();

Samowykonujące się funkcje anonimowe

W języku JavaScript bardzo często wykorzystuję się samowykonujące się funkcje anonimowe. Funkcja tworzona jest wówczas poprzez wyrażenie funkcyjne, a następnie zostaje natychmiastowo wykonana. Rozwiązanie to jest niezwykle przydatne jeśli nie chcesz zaśmiecać globalnej przestrzeni nazw swoim kodem — zmienne zadeklarowane wewnątrz funkcji nie są widoczne poza nią.

Przykład 2.37. Samowykonująca się funkcja anonimowa

(function(){
    var foo = 'Witaj, świecie';
})();


console.log(foo);   // niezdefiniowane!

Funkcja jako argument

W języku JavaScript, funkcje można określić mianem „obywateli pierwszej kategorii” — mogą być one przypisywane do zmiennych lub przekazywane jako argumenty innych funkcji. Przekazywanie funkcji jako argumentu to bardzo częsta praktyka w jQuery.

Przykład 2.38. Przekazywanie funkcji anonimowej jako argumentu

var mojaFn = function(fn) {
    var rezultat = fn();
    console.log(rezultat);
};

mojaFn(function() { return 'witaj, świecie'; });   // rejestruje witaj, świecie

Przykład 2.39. Przekazywanie nazwanej funkcji jako argumentu

var mojaFn = function(fn) {
    var rezultat = fn();
    console.log(rezultat);
};

var mojaDrugaFn = function() {
    return 'witaj, świecie';
};

mojaFn(mojaDrugaFn);   // rejestruje witaj, świecie

Sprawdzanie typu zmiennej

Język JavaScript pozwala na sprawdzenie „typu” danej zmiennej. Rezultaty mogą być jednak dezorientujące — przykładowo typ tablicy to obiekt (object).

Do sprawdzania typu wartości często używany jest operator typeof.

Przykład 2.40. Sprawdzanie typów różnych zmiennych

var mojaFunkcja = function() {
    console.log('witaj');
};

var mojObiekt = {
    foo : 'bar'
};

var mojaTablica = [ 'a', 'b', 'c' ];

var mojCiag = 'witaj';

var mojaLiczba = 3;

typeof mojaFunkcja;   // zwraca function
typeof mójObiekt;     // zwraca object
typeof mojaTablica;      // zwraca object – uwaga!
typeof mójCiąg;     // zwraca string
typeof mojaLiczba;     // zwraca number

typeof null;         // zwraca object – uwaga!

if (mojaTablica.push && mojaTablica.slice && mojaTablica.join) {
    // prawdopodobnie jakaś tablica
    // (jest to tak zwane „kacze typowanie”)
}

if (Object.prototype.toString.call(mojaTablica) === '[object Array]') {
    // To na pewno tablica!
    // Powszechnie uważa się to za najlepszy sposób
    // do określenia, czy dana wartość jest tablicą
}

Biblioteka jQuery zawiera metody pomocnicze, które pozwalają ustalić typ dowolnej wartości. Metody te zostaną omówione w dalszej części kursu.

Słowo kluczowe this

W JavaScripcie, podobnie jak w większości języków programowania obiektowego, this jest specjalnym słowem kluczowym wykorzystywanym w metodach w celu odwołania się do obiektu, na którym dana metoda jest wywoływana. Wartość this określana jest w kilku prostych krokach:

  1. Jeśli funkcja zostaje wywołana metodą Funkcja.call lub Funkcja.apply, this przyjmie wartość pierwszego argumentu przekazanego do metody call/apply. Jeśli pierwszy argument przekazany do metody call/apply ma wartość null lub undefined, this będzie odnosić się do obiektu globalnego (w przeglądarkach internetowych jest to obiekt window).
  2. Jeśli wywoływana funkcja została utworzona przy pomocy metody Funkcja.bind, this przyjmie wartość pierwszego argumentu, który został przekazany do metody bind w czasie tworzenia funkcji.
  3. Jeśli funkcja jest wywoływana jako metoda obiektu, this odwołuje się do tego obiektu.
  4. W pozostałych przypadkach wywoływana jest samodzielna funkcja, niepowiązana z żadnym obiektem — wówczas this odwołuje się do obiektu globalnego.

Przykład 2.41. Funkcja wywołana przy użyciu metody Funkcja.call

var mojObiekt = {
    przywitajSie : function() {
        console.log('Cześć! Mam na imię ' + this.mojeImie);
    },

    mojeImie : 'Rebecca'
};

var drugiObiekt = {
    mojeImie : 'Colin'
};

mojObiekt.przywitajSie();                  // rejestruje Cześć! Mam na imię Rebecca
mojObiekt.przywitajSie.call(drugiObiekt); // rejestruje Cześć! Mam na imię Colin
Przykład 2.42: Funkcja utworzona przy pomocy metody Function.bind
var mojeImie = 'obiekt globalny',

    przywitajSie = function () {
        console.log('Cześć! Mam na imię ' + this.mojeImie);
    },

    mojObiekt = {
        mojeImie : 'Rebecca'
    };

var mojObiektCzesc = przywitajSie.bind(mojObiekt);

przywitajSie();       // rejestruje Cześć! Mam na imię obiekt globalny
mójObiektCześć();  // rejestruje Cześć! Mam na imię Rebecca
Przykład 2.43: Funkcja wiązana z obiektem w czasie działania programu
var mojeImie = 'obiekt globalny',

    przywitajSie = function () {
        console.log('Czesc! Mam na imie ' + this.mojeImie);
    },

    mojObiekt = {
        mojeImie : 'Rebecca'
    },

    drugiObiekt = {
        mojeImie : 'Colin'
    };

mojObiekt.przywitajSie = przywitajSie;
drugiObiekt.przywitajSie = przywitajSie;

przywitajSie();               // rejestruje Cześć! Mam na imię obiekt globalny
mójObiekt.przywitajSie();      // rejestruje Cześć! Mam na imię Rebecca
drugiObiekt.przywitajSie();  // rejestruje Cześć! Mam na imię Colin

Zakres dostępności zmiennych JavaScript

„Zakres” odnosi się do zmiennych dostępnych w danym bloku kodu, w określonym czasie. Niezrozumienie pojęcia zakresu często sprawia, że męczymy się z uciążliwymi usterkami.

Zmienna zadeklarowana wewnątrz funkcji poprzez słowo kluczowe var dostępna jest tylko w obrębie tej funkcji — nie można więc skorzystać z takiej zmiennej poza tą funkcją. Dostęp do tej zmiennej będą mieć natomiast funkcje zdefiniowane wewnątrz tej funkcji.

Ponadto zmienne zadeklarowane wewnątrz funkcji bez słowa kluczowego var nie są dla danej funkcji zmiennymi lokalnymi — JavaScript przeglądnie cały łańcuch zakresu aż do zakresu okna by ustalić, gdzie zmienna ta została zdefiniowana. Jeśli nie znajdzie tej definicji, zmienna ta zostanie potraktowana jako globalna, co może mieć zupełnie nieprzewidywalne konsekwencje.

Przykład 2.44. Funkcje mają dostęp do zmiennych zdefiniowanych w obrębie tego samego zakresu

var foo = 'witaj';

var przywitajSie = function() {
    console.log(foo);
};

przywitajSie();         // rejestruje witaj
console.log(foo);   // również rejestruje witaj

Przykład 2.45. Kod znajdujący się poza zakresem, w którym została zdefiniowana zmienna, nie ma dostępu do tej zmiennej

var przywitajSie = function() {
    var foo = 'witaj';
    console.log(foo);
};

przywitajSie();         // rejestruje witaj
console.log(foo);   // nic nie rejestruje

Przykład 2.46. Zmienne o takiej samej nazwie mogą znajdować się w różnych zakresach i przybierać różne wartości

var foo = 'świecie';

var przywitajSie = function() {
    var foo = 'witaj';
    console.log(foo);
};

przywitajSie();         // rejestruje witaj
console.log(foo);   // rejestruje świecie

Przykład 2.47. Po zdefiniowaniu funkcja może „zobaczyć” zmiany w wartościach zmiennych

var mojaFunkcja = function() {
    var foo = 'witaj';

    var mojaFn = function() {
        console.log(foo);
    };

    foo = 'świecie';

    return mojaFn;
};

var f = mojaFunkcja();
f();  // rejestruje 'świecie' – ups

Przykład 2.48. Zakresy mogą nas często przyprawić o ból głowy

// samowykonująca się funkcja anonimowa
(function() {
    var baz = 1;
    var bim = function() { alert(baz); };
    bar = function() { alert(baz); };
})();

console.log(baz);  // baz nie jest zdefiniowany poza zakresem funkcji

bar();  // bar jest zdefiniowany poza zakresem funkcji anonimowej
        // ponieważ nie został zadeklarowany przy pomocy słowa kluczowego var; ponadto
        // ponieważ został zdefiniowany w tym samym zakresie co baz,
        // bar ma dostęp do baz – zaś inny kod
        // poza zakresem funkcji tego dostępu nie ma

bim();  // bim nie jest zdefiniowany poza zakresem funkcji anonimowej,
        // w rezultacie wystąpi błąd

Zamknięcia (closures)

Zamknięcia stanowią rozszerzenie pojęcia zakresu — funkcje mają dostęp do zmiennych, które były dostępne w zakresie, w którym utworzono te funkcje. Jeśli definicja ta wydaje ci się niejasna, nie przejmuj się: zamknięcia najlepiej zrozumieć na przykładach.

Przykład 2.47 demonstrował w jaki sposób funkcje mają dostęp do zmienionych wartości zmiennych. Podobnie rzecz ma się w przypadku funkcji zdefiniowanych wewnątrz pętli — po zdefiniowaniu funkcja „widzi” zmianę wartości zmiennej i w rezultacie wszystkie kliknięcia wywołują okno alertu z komunikatem 5.

Przykład 2.49. Jak zamknąć wartość i

/* kod nie zachowa się tak, jakbyśmy chcieli; */
/* każde kliknięcie wywoła okno alertu z komunikatem 5 */
for (var i=0; i<5; i++) {
    $('<p>kliknij mnie</p>').appendTo('body').click(function() {
        alert(i);
    });
}

Przykład 2.50. „Zamykamy” wartość i poprzez zamknięcie

/* rozwiązanie: zamykamy wartość i wewnątrz funkcji utwórzFunkcje, aby wartość tej zmiennej nie uległa zmianie */
var utworzFunkcje = function(i) {
    return function() { alert(i); };
};

for (var i=0; i<5; i++) {
    $('<p>kliknij mnie</p>').appendTo('body').click(utworzFunkcję(i));
}

Zamknięcia mogą służyć również do rozwiązywania problemów ze słowem kluczowym this, które dla każdego zakresu jest unikalne:

Przykład 2.51. Jak uzyskać jednoczesny dostęp do wewnętrznego i zewnętrznego obiektu używając domknięcia

var zewnetrznyObj = {
    mojaNazwa : 'zewnętrzny',
    zewnetrznaFunkcja : function () {

        // odniesienie do obiektu zewnetrznyObj jest zamknięte w funkcji wewnetrznaFunkcja
        var self = this;

        var wewnetrznyObj = {
            mojaNazwa : 'wewnętrzny',
            wewnetrznaFunkcja : function () {
                console.log(self.mojaNazwa this.mojaNazwa); // rejestruje 'zewnętrzny wewnętrzny'
            }
        };

        wewnetrznyObj.wewnetrznaFunkcja();

        console.log(this.mojaNazwa); // rejestruje 'zewnętrzny'
    }
};

zewnętrznyObj.zewnetrznaFunkcja();

Autor: Rebecca Murphey

Źródło: http://github.com/rmurphey/jqfundamentals

Tłumaczenie: Łukasz Piwko

Treść tej strony jest dostępna na zasadach licencji CC BY-SA 3.0 US

1 komentarz do “Rozdział 2. Podstawy języka JavaScript”

  1. dzięki za artykuł, z tym ze przez błędy w prostych przykładach trochę to słabo wszystko wypada.
    Bo wydaje się ze rozumiem to co wyjaśniasz a w przykładzie wygląda to inaczej niż powinno w stosunku do tego co sam tłumaczysz.

    Jako poczatkujacy javascriptowiec moge powiedziec ze w co 2 przykladzie sa bledy. Prosze popraw to. Bo wprowadzaja w blad osobe uczaca sie

Możliwość komentowania została wyłączona.