Świat poza jQuery - Karol Rogowski

Kup ebooka

64.00 zł
51.20 zł (39,90 zł najniższa cena z 30 dni)

-
Proszę czekać

Spis treści

Wprowadzenie

Underscore.js

Czym jest Underscore.js?

Dlaczego warto poznać Underscore.js?

Dlaczego tutaj o nim mówię?

Instalacja Underscore.js

Przykłady użycia

_.each(list, iterator)

_.map(list, iterator)

_.reduce(list, iterator, memo)

_.reduceRight(list, iterator, memo)

_.find(list, iterator)

_.filter(list, iterator)

_.where(list, properties)

_.findWhere(list, properties)

_.reduce(list, iterator)

_.every(list, iterator)

_.some (list, iterator)

_.contains(list, value)

_.invoke(list, methodName)

_.pluck(list, propertyName)

_.max(list, iterator) i _.min(list, iterator)

_.sortBy(list, iterator)

_.groupBy(list, iterator)

_.countBy(list, iterator)

_.shuffle(list)

_.size(list)

_.first(array) i _.take(array, n)

_.initial(array, n), _.last(array, n)oraz _.rest(array, index)

_.compact(array)

_.flatten(array, shallow)

_.without(array, values)

_.union(arrays)

_.intersection(arrays)

_.difference(array, array)

_object(list, values)

_.indexOf(array, value) i _.lastIndexOf(array, value)

_.sortedIndex(list, value)

_.range(start, stop, step)

_.bind(function, object, arguments)

_.partial(function, arguments)

_.memoize(function)

_.delay(function, wait, arguments)

_.defer(function, arguments)

_.throttle(function, wait)

_.debounce(function, wait)

_.once(function) i _.after(counter, function)

_.wrap(function, wrapper)

_.compose(functions)

_.chain(object) i _(object).value()

_.tap(object, interceptor)

_.keys(object), _.values(object), _.pairs(object) oraz _.invert(object)

_.functions(object)

_.extend(destination, sources), _.pick(object, keys) oraz _.omit(object, keys)

_.clone(object)

_.has(object, key)

_.isEqual(object1, object2)

_.is(na sto sposobów)

_.noConflict()

_.times(n, iterator) i _.random(min, max)

_.mixin(object)

_.unique(prefix)

_.escape(text) i _.unescape(text)

_.result(object, property)

_.template()

Podsumowanie

Underscore.js

Czym jest Underscore.js?

Underscore.js jest to biblioteka dostarczająca nam użytecznych funkcji podczas pracy w JavaScripcie. Hm, bardzo dokładna i wyczerpująca definicja. A tak na poważnie, jest to biblioteka zawierająca narzędzia umożliwiające usprawnienie naszej pracy podczas wykonywania codziennych zadań programistycznych. Oferuje ona około 80 funkcji, które służą przede wszystkim do zarządzania kolekcjami, obiektami oraz przygotowanymi przez nas funkcjami. Jeden z moich przyjaciół podczas rozmowy na temat Underscore.js powiedział, że "jest to taki Linq przeniesiony do JavaScriptu". Na pewno porównanie to ma w sobie trochę sensu. Jak dużo, to pozostawiam już ocenie własnej każdego czytelnika.

Dlaczego warto poznać Underscore.js?

Powodów można by wymienić wiele. Postaram się skupić na tych, co do których mam największe przekonanie. Po pierwsze, zawsze warto uczyć się nowych rzeczy i poznawać nowe sposoby spojrzenia nawet na te problemy, które są nam już doskonale znane. Dzięki temu zawsze jest szansa, że nauczymy się czegoś ciekawego. Po drugie, operacje na kolekcjach są czymś, czego chyba nikomu z nas nie uda się nigdy uniknąć. Skoro i tak musimy robić to ciągle, róbmy to zarówno możliwie szybko, jak i optymalnie, a jednocześnie bezboleśnie. W omawianej bibliotece otrzymujemy cały zestaw funkcji, które w większości napotkanych scenariuszy na pewno znajdą zastosowanie. Po trzecie, oferuje on bardzo uproszczone i bogate zarządzanie zarówno pojedynczymi obiektami, jak i całymi ich kolekcjami. Tak wiem, zupełnie jak wspomniany przed chwilą Linq. Po czwarte, ... albo nie... już wystarczy.

Przejdźmy do właściwej prezentacji tego, co chciałbym przekazać w tym rozdziale.

Dlaczego tutaj o nim mówię?

O Underscore.js piszę tutaj z bardzo prostego powodu. Jest to rozwiązanie bardzo użyteczne, dzięki któremu tempo oraz jakość pracy każdego programisty JavaScriptu są lepsze. To chyba wystarczy, jeżeli chodzi o wyjaśnienia.

Instalacja Underscore.js

W przypadku Underscore.js można powiedzieć, że słowo instalacja zostało użyte na wyrost. Wynika to głównie z faktu, że jest to tak naprawdę tylko jeden plik. Plik ten można znaleźć na stronie domowej projektu, pod adresem: http://underscorejs.org/. Znajduje się tam oczywiście o wiele więcej niż tylko pliki z biblioteką, ale skupimy się tylko na nim.

W czasie pisania książki na stronie projektu mogliśmy wybierać z trzech rodzajów materiałów do pobrania. Pierwszy z nich - Dewelopment Version (1.5.1) - to pełna wersja biblioteki. Powinniśmy jej używać, kiedy pracujemy nad czymś lokalnie. Dzięki temu w każdej chwili możemy prześledzić sposób implementacji każdej części biblioteki. Co prawda można to też zrobić, jeżeli ktoś jest bardzo uparty, na wersji zminimalizowanej. Wersję tę możemy pobrać, klikając Production Version (1.5.1). Jeżeli ktoś jeszcze nie miał do czynienia z praktyką wystawiania dwóch wersji bibliotek JavaScript, to wyjaśnię, że chodzi w niej tylko o to, aby do środowiska produkcyjnego wrzucać jak najmniej kodu. Dzięki czemu nasze rozwiązanie będzie nadal działało poprawnie, a ładować się będzie szybciej. Proces taki nazywa się obfuskacją (zaciemnianiem) kodu. Trzecim plikiem, który możemy pobrać, jest Edge Version. Jest to wersja, nad którą właśnie toczą się prace i która nie została jeszcze oficjalnie udostępniona. Zdecydowanie nie zalecam korzystania z niej podczas tworzenia rozwiązań przeznaczonych do użytku komercyjnego, w których stabilność jest rzeczą absolutnie nadrzędną. Polecam natomiast korzystanie z niej w celach edukacyjnych i kiedy programujemy po prostu dla siebie.

Wystarczy teraz wybrać plik, który nas interesuje, zapisać go i dodać odniesienie do niego w potrzebnym projekcie. Nic prostszego.

Zanim przejdziemy do praktycznym przykładów, chciałbym pokazać, jak drastycznie różni się wygląd funkcji w wersji pełnej od tego w wersji produkcyjnej i co za tym idzie, dlaczego analiza tego drugiego jest zajęciem dla bardzo wytrwałych. Pokażę to na przykładzie funkcji each.

Listing 1. Implementacja funkcji each w pełnej wersji

// The cornerstone, an 'each' implementation, aka 'forEach'.

// Handles objects with the built-in 'forEach', arrays, and raw objects.

// Delegates to **ECMAScript 5**'s native 'forEach' if available.

var each = _.each = _.forEach = function(obj, iterator, context) {

if (obj == null) return;

if (nativeForEach && obj.forEach === nativeForEach) {

obj.forEach(iterator, context);

} else if (obj.length === +obj.length) {

for (var i = 0, l = obj.length; i < l; i++) {

if (iterator.call(context, obj[i], i, obj) === breaker) return;

}

} else {

for (var key in obj) {

if (_.has(obj, key)) {

if (iterator.call(context, obj[key], key, obj) === breaker) return;

}

}

}

};

Listing 2. Implementacji funkcji each w wersji produkcyjnej

j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(j.has(n,a)&&t.call(e,n[a],a,n)===r)return};

Jak widać już na pierwszy rzut oka, druga wersja jest praktycznie nieczytelna. Złożyło się na to zarówno odpowiednie formatowanie pisanego przez nas kodu, jak i stosowanie możliwie najkrótszych nazw używanych zmiennych. Otrzymaliśmy oczywiście dzięki temu znacznie mniejsze pliki. Natomiast pierwsza przedstawiona wersja jest w pełni czytelna i przejrzysta. Możemy prześledzić zarówno sposób implementacji, jak i zapoznać się z komentarzami pozostawionymi przez autora.

Przykłady użycia

Mógłbym w tym momencie utworzyć stronę internetową, dodać do niej Underscore.js i na tym prezentować przykłady jej możliwości. Jednak skoro jednym z głównych celów tej książki jest pokazanie możliwie największej liczby ciekawych rozwiązań związanych z JavaScriptem, zaprezentuję to trochę inaczej. Możli­wości biblioteki przedstawię przy użyciu jsfiddle.net. Cóż to w ogóle takiego? Jest narzędzie (strona), które umożliwia testowanie i tworzenie tymczasowych rozwiązań z zakresu technologii webowych. Możemy je znaleźć pod adresem http://jsfiddle.net/. Wejdźmy więc na tę stronę i zobaczmy, co możemy tam znaleźć.

Omówmy pokrótce, co widzimy na tej stronie (rys. 1). Rzuca się w oczy, że niemalże całość strony zajmują cztery okna. W tym momencie warto też wspomnieć, że wypełnianie któregokolwiek z nich jest opcjonalne i można korzystać tylko z tych, które nas w danym momencie interesują. Pierwsze z nich jest przeznaczone na kod HTML tworzący nasza stroną. Następne to miejsce na CSS. Funkcji tej części raczej nie trzeba nikomu objaśniać. Ostatnim z miejsc do wprowadzania danych jest okno JavaScript. Umieszczamy tutaj skrypt, który chcemy, aby był wykonany na naszej stronie. Oczywiście, z racji specyfiki omawianego tematu, to właśnie w nim będziemy wykonywać większość naszej pracy. Ostatnim oknem, które co prawda nie służy bezpośrednio do wprowadzania treści, jest okno Result. Postaramy się z niego nie korzystać i wyniki naszej pracy obserwować w narzędziu deweloperskim. W tym przypadku będzie to narzędzie dostarczone nam razem z przeglądarką Google Chrome. Można ją uruchomić, naciskając F12.

Zanim jednak do tego dotrzemy, przynajmniej pobieżnie omówmy, co jeszcze jest dostępne w narzędziu jsfiddle. Skupmy się na jego górnym pasku menu. Kolejno od lewej strony widzimy tam najpierw przycisk Run. Jak łatwo się domyśleć, służy on do uruchamiania przygotowanych przez nas materiałów. Następnie mamy przycisk Save. I tu robi się już ciekawie, ponieważ dzięki niemu możemy nie tylko zapisać przygotowany przez nas projekt, lecz także mamy możliwość późniejszej rozbudowy czy też przesłania go komuś jako materiał poglądowy. Kolejne dwa elementy służą do tworzenia lepszego kodu. Pierwszy z nich - TidyUp - umożliwia uporządkowanie zarówno tego, co napisaliśmy, jak i ogólnej organizacji kodu. Przycisk JSHint, jak sama nazwa wskazuje, uruchamia narzędzie, które dostarcza uwagi dotyczące ściśle jakości naszego kodu JavaScript. Podczas pracy polecam korzystanie z obu tych narzędzi.

Rysunek 1. Strona projektu jsfiddle.net

Dalej są dwa przyciski, które co prawda nie służą do bezpośredniej pracy, ale warto wiedzieć do czego można ich użyć. Pierwszy z nich uruchamia coś w rodzaju tablicy ogłoszeń o pracy. Oferty są naprawdę bardzo ciekawe, z tym że na razie jeszcze nie widziałem niestety żadnej z Polski. Ostatni przycisk służy do zalogowania się do rozwiązania JsFiddle. Dzięki temu, tak jak w większości aplikacji, prezentowana zawartość będzie dostosowana do naszego projektu.

A co jest w kolumnie po lewej? Jej elementy dotyczą aspektów czysto technicznych tworzonego projektu. Pierwszy daje nam możliwość wybrania biblioteki oraz sposobu, w jaki nasz skrypt zostanie umieszczony na stronie. My oczywiście wybieramy najnowszą wersję Underscore.js - 1.4.4, a jeżeli chodzi o umieszczenie skryptu, to możemy pozostawić onLoad. Kolejny element - Fiddle Options - służy do ustawienia wartości opisujących to, co w danym momencie tworzymy. Następnie mamy zakładkę, która jest nieodzowna, jeżeli zamierzamy stworzyć coś nieco większego lub niestandardowego. Mówię tutaj o zakładce External Resources (materiały zewnętrzne). Możemy dzięki niej dodawać odnośniki do skryptów lub też stylów, których potrzebujemy. Fakt, że jest to niesamowicie przydatne narzędzie, nie wymaga raczej jakiegoś większego tłumaczenia.

Następna zakładka - Languages - umożliwia nam dobranie języków programowania, jakich chcemy używać. Możemy na przykład zamiast JavaScriptu użyć CoffeeScriptu. Wybór nie jest jakiś przesadnie duży. Mamy dwie możliwości, jeżeli chodzi o style, i trzy w przypadku skryptów klienckich. Dalej jest zakładka dotycząca żądań ajaksowych - Ajax Requests. Można znaleźć tutaj pełną dokumentację oraz przykłady, które pomogą nam opanować to zagadnienie. Przedostatnia zakładka - Examples - służy do prezentowania przykładów zastosowań narzędzia, jakim jest jsfiddle. I ostatnia - Legal, Credits, and Links - dostarcza informacje o statusie prawnym narzędzia, z którego chcemy korzystać.

Przejdźmy teraz do faktycznej nauki Underscore.js. Na początku chciałbym zauważyć, że Underscore.js to jedyna biblioteka, której stosowania nie będziemy się uczyć na mniejszych lub większych projektach. Wynika to z dwóch kwestii. Pierwszą z nich jest jej specyfika. Nie narzuca nam ona żadnej organizacji naszej aplikacji. Dostarcza jedynie zestaw metod, których powinniśmy używać w stosownych sytuacjach. Zupełnie jak wspomniany już LINQ. Drugą natomiast jest to, że bardzo zależy mi na zaprezentowaniu tego rozwiązania, ponieważ jest ono naprawdę wyjątkowo przydatne, jeżeli wiemy, kiedy i jak go używać. Podczas tworzenia jakiegoś konkretnego projektu wielu ciekawych metod nie udało by się pokazać lub po prostu gdzieś umknęłyby w kodzie.

Rysunek 2. Konfiguracja przeglądarki do nauki Underscore.js

W jaki sposób będzie wyglądało prezentowanie i omawianie kolejnych metod Underscore.js? Otóż bardzo prosto. Na początku omówimy teoretycznie, do czego powinna być używana dana metoda. Potem zapiszemy kod ilustrujący jej możliwości i faktyczne działanie. Jak mówi stare przysłowie "Wiersz kodu objaśnia więcej niż tysiąc słów". Na końcu, jeżeli nasz kod zadziała poprawnie, zobaczymy jego wyniki wypisane w konsoli narzędzia deweloperskiego. Okno przeglądarki, jeżeli oczywiście chcesz robić przykłady razem ze mną, powinno wyglądać jak na rysunku 2. Należy zwrócić uwagę na dwa kluczowe elementy: czy Underscore.js została dodana do naszego projektu oraz czy nasze narzędzie jest otwarte wraz z oknem konsoli.

Skoro wszystko jest już jasne i skonfigurowane, możemy wziąć się do poznawania Underscore.js.

_.each(list, iterator)

Funkcja each jest używana w sytuacjach, w których mamy wykonać pewną operację na wszystkich elementach danej kolekcji. Pierwszym parametrem funkcji jest kolekcja elementów, na których będziemy wykonywać operację, drugim - funkcja definiująca, co ma być wykonane na wejściowych elementach.

Jako element wejściowy weźmiemy tablicę z obiektami zawierającymi dane o samochodach. Dla każdego obiektu wyświetlmy w konsoli odpowiednio sformatowane zdanie.

Listing 3. Użycie funkcji each

var cars = [{

name: 'bmw,

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}];

var showInfo = function (car) {

console.log(car.name + " from " + car.year + " costs " + car.ost);

};

_.each(cars, showInfo);

Rysunek 3. Wynik działania funkcji each

Najpierw jest definiowana kolekcja elementów wejściowych. Następnie, przy użyciu funkcji showInfo, na konsoli są wyświetlane informacje przekazane przez obiekt wejściowy. Ostatnią część kodu stanowi wywołanie metody each.

Na rysunku 3 widzimy, że dla każdego elementu przekazanego do funkcji showInfo otrzymaliśmy oddzielny wpis w konsoli. Za chwilę porównamy uzyskany wynik z wynikiem działania funkcji map, która w pewien sposób działa podobnie do funkcji each.

_.map(list, iterator)

Funkcji map używamy wówczas, gdy chcemy zrzutować jedną kolekcję elementów na drugą. Jako pierwszy parametr przyjmuje ona kolekcję wejściową. W naszym przypadku będzie to kolekcja obiektów mających po trzy właściwości każdy. Drugim jej parametrem jest funkcja, która dokonuje operacji na kolejnych elementach z kolekcji i zwraca nam element wyjściowy. Na koniec funkcja map z tych obiektów wyjściowych tworzy kolekcję, która ostatecznie stanowi wynik jej działania.

Wykorzystamy znaną nam już kolekcję samochodów i spróbujemy zmapować ją do tablicy napisów, która potem zostanie wyświetlona w konsoli.

Listing 4. Użycie funkcji map

var cars = [{

name: 'bmw',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}];

var mergeFunction = function (car) {

return car.name + " from " + car.year + " costs " + car.cost;

};

console.log(_.map(cars, mergeFunction));

Rysunek 4. Wynik działania funkcji map

Na początku listingu 4 mamy zdefiniowaną tablicę elementów wejściowych. Następnie jest funkcja mergeFunction, która będzie realizować mapowanie obiektów. Na końcu jest wywołanie opisywanej funkcji map, z kolekcją wejściową oraz funkcją mapującą, którego wynik zostaje wyświetlony w konsoli.

Jak widać na rysunku 4, tym razem otrzymany wynik to już nie są pojedyncze wpisy, tak jak w przypadku funkcji each, ale kolekcja zmapowanych elementów.

_.reduce(list, iterator, memo)

Funkcja reduce jest używana do zredukowania wszystkich elementów wejściowych do jednej wartości wyjściowej. Przykładem takiego działania może być dodawanie lub mnożenie przez siebie wszystkich elementów z kolekcji. Mogłoby się wydawać, że taki sam rezultat dałoby się osiągnąć, pisząc bardzo prostą pętlę. Tak, tylko że diabeł tkwi w szczegółach. Szczegółem tym jest element memo. Jest to obiekt przechowujący poprzednie wykonanie funkcji. Otrzymujemy dzięki temu dwa efekty. Po pierwsze, mamy dostęp do wartości z poprzedniej iteracji. Nie jest to oczywiście przydatne zawsze, niemniej jednak bywają sytuacje, w których jest nam to potrzebne. Po drugie, dzięki zabiegom w implementacji funkcji reduce wszelkie operacje wewnątrz niej wykonują się bez niepotrzebnych opóźnień, co jest bardzo ważną kwestią, gdyż kod jest wówczas napisany w sposób optymalny. I jest to właśnie doskonały przykład na pokazanie jednego z głównych celów omawianej biblioteki, czyli sprawienia, że będziemy pisać lepszy kod.

Nasz przykładowy kod będzie realizował sumowanie wszystkich elementów znajdujących się w tablicy. Na konsoli zobaczymy zarówno wartość po każdej iteracji sumowania, jak i ostateczny wynik.

Listing 5. Użycie funkcji reduce

var sumValues = [1, 2, 3, 4, 5, 6];

var sumFunction = function (memo, number) {

if (memo) {

console.log('Adding ' + memo + ' and ' + number);

}

return memo + number;

};

console.log(_.reduce(sumValues, sumFunction));

Rysunek 5. Wynik działania funkcji reduce

W powyższym przykładzie na początku jest definiowany zestaw wartości, na których będą wykonywane działania. Następnie jest tworzona funkcja, która sumuje elementy. Warto zwrócić uwagę na sposób fizycznego sumowania elementów (return memo + numer). Jest ono zaimplementowane zgodnie z zaleceniami twórców Underscore.js, czyli przy użyciu elementu memo. Jeżeli chodzi o wyświetlanie wartości, to wyświetlamy tutaj każdą iterację, jeśli zdefiniowany jest element memo, oraz ostateczny wynik sumowania.

Jak widać na rysunku 5, nasz kod zachował się tak, jak się tego spodziewaliśmy. Zostały zapisane kolejno zarówno wszystkie operacje sumowania, jak i ostateczny wynik.

_.reduceRight(list, iterator, memo)

Funkcja ta jest niemal identyczna z omawianą w poprzednim punkcie. Od zwykłej funkcji reduce różni się ona tylko tym, że elementy są przeglądane od końca. Dlatego właśnie jej działanie zaprezentujemy w możliwie najprostszy sposób. Zamieniamy wywołanie funkcji reduce z poprzedniego przykładu na wywołanie reduceRight.

Listing 6. Użycie funkcji reduceRight

var sumValues = [1, 2, 3, 4, 5, 6];

var sumFunction = function (memo, number) {

if (memo) {

console.log('Adding ' + memo + ' and ' + number);

}

return memo + number;

};

console.log(_.reduceRight(sumValues, sumFunction));

Rysunek 6. Wynik działania funkcji reduceRight

Jak widać, funkcja faktycznie wykonała się "od tyłu" i, co jest oczywiste, dała ostatecznie taki sam wynik jak tak prezentowana poprzednio.

_.find(list, iterator)

find jest pierwszym omawianym przykładem funkcji, której nazwa może być trochę myląca. Moim zdaniem o wiele lepsza byłaby nazwa first. Zaraz przekonamy się dlaczego. Funkcja ta przyjmuje dwa argumenty. Pierwszym z nich jest kolekcja elementów, w której będziemy czegoś szukać, a drugim - funkcja zwracająca prawdę lub fałsz. I tutaj może pojawić się pewne nieporozumienie. Wynikiem działania find nie jest kolekcja elementów, dla których funkcja wejściowa zwróciła prawdę, lecz pierwszy taki element. Dlatego właśnie nazwa first byłaby chyba jednak lepsza.

Spróbujmy wywołać funkcję find tak, aby znalazła pierwszy element większy od 3.

Listing 7. Użycie funkcji find

var searchValues = [1, 2, 3, 4, 5, 6];

var searchFunction = function (number) {

return number > 3;

};

console.log(_.find(searchValues, searchFunction));

Rysunek 7. Wynik działania funkcji find

Na początku naszego przykładu definiujemy kolekcję liczb, którą będziemy przeszukiwać. Następnie jest funkcja, która dla zadanej wartości zwraca, czy porównywana wartość jest większa od trzech. Na końcu widzimy wywołanie funkcji find, która korzysta z dwóch utworzonych wcześniej elementów.

Jak widać, otrzymaliśmy zamierzony rezultat. Zobaczyliśmy na konsoli wartość 4, czyli wartość pierwszego elementu spełniającego funkcję, według której wyszukiwaliśmy.

_.filter(list, iterator)

Funkcja filter służy do filtrowania kolekcji zadanych elementów przy użyciu zdefiniowanej w tym celu funkcji. Ma ona dwa parametry wejściowe. Pierwszym z nich jest tablica elementów, które będziemy filtrować, a drugim - funkcja określająca warunki, które musi spełniać element, żeby przejść proces filtrowania.

W przykładzie (listing 8) posłużymy się kolekcją liczb z poprzedniej metody. Zmienimy oczywiście funkcję z Underscore na aktualnie omawianą.

Listing 8. Użycie funkcji filter

var searchValues = [1, 2, 3, 4, 5, 6];

var searchFunction = function (number) {

return number > 3;

};

console.log(_.filter(searchValues, searchFunction));

Rysunek 8. Wynik działania funkcji filter

Jak widać, funkcja wykonała się prawidłowo. Należy zwrócić uwagę na fakt, że tym razem otrzymaliśmy nie pojedynczy element, lecz całą ich tablicę.

_.where(list, properties)

Where jest kolejną metodą z biblioteki Underscore, która służy do wybierania interesujących nas obiektów z listy. Pierwszym jej parametrem jest oczywiście lista obiektów, którą będziemy przeszukiwać. Obiekty te muszą mieć pola zdefiniowane jako klucz-wartość (key-value). Drugim parametrem jest tym razem nie funkcja, lecz obiekt. Obiekt ten również powinien zawierać zestaw klucz-wartość. W przypadku tej funkcji wyszukiwanie polega na znalezieniu obiektów, których wspomniane zestawy klucz-wartość pasują do siebie.

Do zademonstrowania działania metody where posłuży znana już tablica zawierająca nazwy samochodów, do której został dodany jeszcze jeden element. Naszym celem będzie znalezienie wszystkich samochodów marki Audi.

Listing 9. Użycie funkcji where

var cars = [{

name: 'bmw',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

console.log(_.where(cars, {

name: 'Audi'

}));

Rysunek 9. Wynik działania funkcji where

W kodzie z listingu 9 widzimy, że najpierw tworzona jest lista czterech samochodów, a następnie przy użyciu obiektu filtrującego oraz funkcji where wybieramy z nich tylko Audi.

Jak widać, funkcja zadziałała zgodnie z naszymi oczekiwaniami. Otrzymaliśmy w wyniku dwa elementy. Funkcja where jest jednak na tyle elastyczna, że pozwala filtrować przy użyciu obiektów filtrujących, które mają dowolną liczbę pól. Spróbujmy tak zdefiniować nasz filtr, aby wyszukał nam audi, ale tylko z 2013 roku. Aby to zrobić, należy w obiekcie odpowiedzialnym za warunki filtrowania dodać kolejne pole identyfikujące rok.

Listing 10. Użycie funkcji where z rozbudowanym obiektem filtrującym

var cars = [{

name: 'BMW',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

console.log(_.where(cars, {

name: 'Audi',

year: 2013

}));

Rysunek 10. Wynik działania funkcji where z rozbudowanym obiektem filtrującym

Jak widać, funkcja where poradziła sobie bez problemu, nawet gdy filtr był nieco bardziej skomplikowany.

_.findWhere(list, properties)

Funkcja findWhere to dosłowne połączenie działania dwóch omawianych już funkcji: find i where. Argumenty wejściowe są identyczne z tymi z klasycznej funkcji where, czyli podajemy kolekcję obiektów oraz obiekt filtrujący. Natomiast z funkcji find zapożyczono zwracanie tylko pierwszego elementu.

Skorzystamy z poprzedniego przykładu, w którym z listy samochodów wyszukiwaliśmy audi.

Listing 11. Przykład użycia funkcji findWhere

var cars = [{

name: 'BMW',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

console.log(_.findWhere(cars, {

name: 'Audi'

}));

Rysunek 11. Wynik użycia funkcji findWhere

Jak widać na rysunku 11, omawiana funkcja faktycznie wybiera pierwszy element z kolekcji przy użyciu przekazanego filtru.

_.reduce(list, iterator)

Funkcja reduce jest, najprościej mówiąc, przeciwieństwem funkcji filter. Służy ona do usunięcia z wynikowej kolekcji tych elementów, które spełniają warunek zawarty w funkcji filtrującej.

W przykładzie zamiast kolekcji liczb użyjemy kolekcji marek samochodów i za pomocą funkcji reduce postaramy się ją odfiltrować. Spróbujemy zrobić to w taki sposób, aby nie pozostało w niej żadne audi.

Listing 12. Użycie funkcji reduce

var cars = [{

name: 'BMW',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nnissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

var searchFunction = function (item) {

return item.name === 'audi';

};

console.log(_.reject(cars, searchFunction));

Rysunek 12. Wynik użycia funkcji reduce

Jak można zauważyć, funkcja reduce faktycznie wykonuje tak zwane odwrotne filtrowanie.

_.every(list, iterator)

Funkcja every umożliwia nam sprawdzenie, czy wszystkie elementy w kolekcji spełniają jakiś warunek. Przyjmuje ona dwa parametry. Pierwszym z nich jest lista elementów, na których będziemy operować. Drugim jest funkcja określająca warunek, który musi spełnić każdy z tych elementów.

W naszym przypadku kolekcją elementów będzie znana nam już lista samochodów. Natomiast funkcja będzie sprawdzać, czy samochód to audi oraz czy został wyprodukowany później niż w 2009 roku.

Listing 13. Użycie funkcji every

var cars = [{

name: 'BMW',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

var searchFunction1 = function (item) {

return item.name === 'Audi';

};

var searchFunction2 = function (item) {

return item.year >= 2010 ;

};

console.log(_.every(cars, searchFunction1));

console.log(_.every(cars, searchFunction2));

Rysunek 13. Wynik użycia funkcji every

Jak możemy zauważyć, oba wywołania omawianej funkcji dały nam oczekiwany wynik. Po pierwsze, nie wszystkie samochody w naszej kolekcji to audi, a po drugie, każdy z wymienionych samochodów został wyprodukowany po 2009.

_.some (list, iterator)

Można powiedzieć, że funkcja some swoim działaniem dopełnia funkcję every. W wyniku wykonania every otrzymujemy true, gdy wszystkie elementy kolekcji spełniają zawarty w niej warunek, a some daje w wyniku true, gdy warunek jest spełniony przez przynajmniej jeden element kolekcji.

Działanie tej funkcji również omówimy na dwóch przykładach. W pierwszym z nich sprawdzimy, czy przynajmniej jeden samochód to audi, a w drugim - czy przynajmniej jeden został wyprodukowany przez 2010 rokiem.

Listing 14. Użycie funkcji some

var cars = [{

name: 'BMW',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

var searchFunction1 = function (item) {

return item.name === 'Audi';

};

var searchFunction2 = function (item) {

return item.year < 2010 ;

};

console.log(_.some(cars, searchFunction1));

console.log(_.some(cars, searchFunction2));

Rysunek 14. Wynik użycia funkcji some

Jak widać na konsoli, aby funkcja some zwróciła nam pozytywny wynik, wystarczy, aby przynajmniej jeden element z kolekcji spełniał zadany warunek.

_.contains(list, value)

Funkcja contains służy do sprawdzenia, czy kolekcja zawiera jakąś wartość. Ma ona dwa argumenty wejściowe. Pierwszym z nich jest kolekcja elementów, w której będziemy szukać, a drugim - wartość, której istnienie w danej kolekcji będziemy sprawdzać.

W celach demonstracyjnych posłużymy się używaną już kolekcją liczb i sprawdzimy, czy należą do niej liczby 3 i 9.

Listing 15. Użycie funkcji contains

var searchValues = [1, 2, 3, 4, 5, 6];

console.log(_.contains(searchValues, 3));

console.log(_.contains(searchValues, 9));

Rysunek 15. Wynik użycia funkcji contains

Funkcja prawidło rozpoznała, czy element należy do zbioru, czy też nie.

_.invoke(list, methodName)

Metoda invoke jest chyba najbardziej skomplikowaną wśród omawianych do tej pory. Przyjmuje ona dwa parametry. Pierwszym jest kolekcja elementów, a drugim - nazwa metody z obiektu kolekcji, którą mamy wywołać. Brzmi trochę zagmatwanie, ale jak się okaże w przedstawionym poniżej przykładzie, nie jest tak strasznie. Przykład przedstawiony na stronie projektu (http://underscorejs.org/#invoke) pokazuje nam, że gdy przekażemy jako pierwszy parametr tablicę tablic liczb, możemy ją sortować, wywołując metodę sort.

Sprawa robi się już nieco ciekawsza, jeżeli zechcemy podać własne obiekty i wywołać napisaną przez nas metodę. Spójrzmy na kod poniżej. Ilustruje on właśnie taką sytuację.

Listing 16. Użycie metody invoke

var CarObject = function (car) {

showCarInfo = function () {

return "Car name is " + car.name;

};

return {

showCarInfo: showCarInfo

};

};

var cars = [new CarObject({

name: 'BMW'

}),new CarObject({

name: 'Audi'

}),new CarObject({

name: 'Nissan'

})];

console.log(_.invoke(cars, 'showCarInfo'));

Rysunek 16. Wynik użycia funkcji invoke

To, co widzimy na początku, jest tworem podobnym do klasy. Posługując się terminologią języków obiektowych, powiemy, że jako konstruktor przyjmuje ona obiekt - markę samochodu, a dostarcza metodę, która zwraca napis. Jest to, moim zdaniem, dość ciekawa konstrukcja. Jeżeli nie spotkałeś się jeszcze z tego typu zapisem, to radzę go dobrze przestudiować.

Kolejne wiersze to tworzenie wejściowej tablicy, a w końcu wywołanie naszej omawianej funkcji. Ma ona za zadanie wywołać na każdym obiekcie w kolekcji jego metodę.

Jak widać, wynikiem jest tablica, w której dla każdego elementu wejściowego został zwrócony rezultat jego własnej funkcji wywołanej na nim samym. Bardzo ciekawa sztuczka.

_.pluck(list, propertyName)

Muszę przyznać, że funkcja pluck została naprawdę ciekawie pomyślana. Jest prosta i bardzo przydatna. Przyjmuje ona dwie wartości. Pierwszą z nich jest kolekcja obiektów zawierających pary klucz-wartość. Drugim elementem jest nazwa jednego z tych kluczy. Funkcja pluck zwraca listę elementów składająca się tylko z wartości przypisanych do tych kluczy. Sprytne, prawda?

Jej działanie pokażemy oczywiście na przykładzie naszej listy samochodów. Będziemy chcieli wypisać tylko ich marki.

Listing 17. Użycie funkcji pluck

var cars = [{

name: 'BMW',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

console.log(_.pluck(cars, 'name'));

Rysunek 17. Wynik użycia funkcji pluck

Widać, że funkcja faktycznie zwróciła tylko elementy mówiące o nazwie marki samochodu z listy.

_.max(list, iterator) i _.min(list, iterator)

max oraz min - ich nazwy mówią chyba wszystko. Podajemy tablicę liczb, a funkcja zwraca wartość maksymalną lub minimalną. Nie ma potrzeby głębiej się nad tym zastanawiać. Sytuacja wygląda już nieco ciekawiej, jeżeli chodzi o kolekcje obiektów. Tutaj musimy podać jeszcze dodatkowo funkcję, która dla każdego obiektu zwraca wartość traktowaną jako wyznacznik maksimum i minimum.

Spróbujmy więc dla naszej kolekcji samochodów znaleźć element, którego rok produkcji jest liczbą najmniejszą oraz największą.

Listing 18. Użycie funkcji max/min

var cars = [{

name: 'BMW',

cost: '10.000$',

year: 2013

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

var criteriaValue = function(car)

{

return car.year;

};

console.log(_.max(cars, criteriaValue));

console.log(_.min(cars, criteriaValue));

Rysunek 18. Wynik działania funkcji max/min

Jak widać na konsoli, otrzymaliśmy w wyniku dwa elementy. To, że pojawił się tam nissan, to żadne zaskoczenie. Był najstarszy, więc liczba określająca jego rok produkcji jast najmniejsza. Natomiast audi - to już może być pewne zaskoczenie. Po pierwsze, był już przed nim element z tym samym rokiem produkcji. Po drugie, dwa elementy mają rok 2013. Wniosek: funkcje typu max/min dają w wyniku ostatni element spełniający kryteria. Warto to zapamiętać.

_.sortBy(list, iterator)

W pierwszym odruchu można by zapytać, czy funkcja sortBy jest nam w ogóle potrzebna. Każdy na pewno wie, że tablice w JavaScripcie mają od razu wbudowaną funkcję sortowania. Otóż sortBy idzie jakby o krok dalej. Umożliwia ona podanie wartości (oczywiście w jakiś sposób powiązanej z parametrem wejściowym), od której ma się odbyć sortowanie.

Zanim jednak rozpatrzymy przykład, omówmy parametry wejściowe funkcji sortBy. Pierwszym z nich jest oczywiście kolekcja elementów, które mają być sortowane, drugim powinna być funkcja określająca, co jeszcze należy zrobić z parametrem wejściowym, zanim faktycznie zostanie on poddany sortowaniu.

Jako przykład weźmiemy tablicę zawierającą jakieś napisy. Zwykle, gdybyśmy wywołali na niej metodę sortującą, zwrócony wynik byłby posortowany alfabetycznie. My jednak będziemy chcieli być oryginalni i postaramy się zwrócić wynik posortowany według długości tych napisów.

Listing 19. Użycie funkcji sortBy

var words = ['Samochod', 'Dom', 'Mieszkanie', 'Komputer', 'Powietrze'];

var sortValue = function (value) {

return value.length;

};

console.log(_.sortBy(words, sortValue));

Rysunek 19. Wynik użycia funkcji sortBy

Na początku przykładowego kodu widać oczywiście listę słów, które chcemy sortować. Następnie mamy zdefiniowaną funkcję, która stanowi esencję działania metody sortBy. Przyjmuje ona, co prawda, jako wartość wejściową ciąg znaków, ale zauważmy, że zwraca już liczbę, która mówi o długości słowa i to po tej wartości odbywa się sortowanie.

Co ciekawego w uzyskanym wyniku? Na pewno to, że posortowaliśmy napisy nie alfabetycznie i że nasze kryterium sortowania faktycznie zadziałało. Zwróćmy jednak uwagę na dwa pierwsze elementy, które są wyświetlone. Są one posortowane według długości, lecz nie alfabetycznie. To oznacza, że mechanizm nie stara się nam dodatkowo pomagać, wykonując jakieś domyślne sortowania. I bardzo dobrze.

_.groupBy(list, iterator)

groupBy, jak łatwo się domyśleć, służy do grupowania w jakiś sposób elementów. Pierwszym jej parametrem jest kolekcja elementów, które mają być grupowane, drugim jest funkcja, która dla każdego elementu z kolekcji zwraca jego wartość grupującą. Podobnie jest w przypadku języków bazodanowych. Tam też nie musieliśmy grupować bezpośrednio po wartościach pól obiektów, ale mogliśmy dopiero po wyniku funkcji, której te właśnie pola były argumentem wejściowym.

Wykonamy na przykład grupowanie po nazwach znanej nam listy samochodów. Będziemy potrzebowali do tego dwóch elementów. Pierwszym będzie oczywiście posiadana przez nas kolekcja samochodów, drugim - funkcja zwracająca nazwę. Oczywiście mogłaby ona przez zwróceniem wartości dokonać na niej jakiś zmian, gdy zachodziła taka potrzeba.

Listing 20. Użycie funkcji groupBy

var cars = [{

name: 'bmw',

cost: '10.000$',

year: 2013

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

var groupValue = function (car) {

return car.name;

};

console.log(_.groupBy(cars, groupValue));

Rysunek 20. Wynik użycia funkcji groupBy

Jak widać, grupowanie przebiegło pomyślnie. Warto zauważyć, że otrzymany wynik jest w bardzo ciekawej postaci. Mianowicie, naszą główną zwróconą wartoś­cią jest obiekt. Zawiera on pary klucz-wartość. Kluczami są w tym przypadku unikatowe wartości zwracane przez naszą funkcję grupującą, a wartości w tej parze są zebrane w tablicy z obiektami kwalifikującymi się do tej właśnie grupy. Wynik może nieco skomplikowany, ale bardzo prosty w ewentualnej programistycznej interpretacji.

_.countBy(list, iterator)

Funkcja countBy działa bardzo podobnie do omawianej przed chwilą funkcji groupBy. Różnicę stanowi tylko zwracany ostatecznie wynik, ponieważ w jej przypadku otrzymamy liczbę obiektów w poszczególnych grupach, a nie faktyczne elementy.

Dlatego właśnie jako przykład weźmiemy kod omawiany ostatnio (z listingu 20) i zmienimy w nim tylko wywoływaną funkcję.

Listing 21. Użycie funkcji countBy

var cars = [{

name: 'bmw',

cost: '10.000$',

year: 2013

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

var groupValue = function (car) {

return car.name;

};

console.log(_.countBy(cars, groupValue));

Rysunek 21. Wynik użycia funkcji countBy

Faktycznie widać, że wynik jest podobny do tego otrzymanego przy użyciu funkcji groupBy. Budowa obiektu jest niemal identyczna. Jedyne co się zmieniło, to wartość w parze klucz-wartość.

_.shuffle(list)

W zasadzie trudno jest powiedzieć na temat tej funkcji cokolwiek odkrywczego. Przyjmuje ona kolekcję i zwraca kolekcję pomieszaną. Warto zwrócić uwagę, że zwracana kolekcja jest kopią, a ta wejściowa się nie zmienia.

Jako przykła weźmiemy kolekcję dziesięciu kolejnych liczb i po prostu ją "potasujmy". Wykonamy to cztery razy, ale o tym, dlaczego tak robimy, powiem za chwilę.

Listing 22. Użycie funkcji shuffle

var numbers = [1,2,3,4,5,6,7,8,9,10];

console.log(_.shuffle(numbers));

console.log(_.shuffle(numbers));

console.log(_.shuffle(numbers));

console.log(_.shuffle(numbers));

Rysunek 22. Wynik użycia funkcji shuffle

Jak widać, liczby zostały bardzo ładnie pomieszane. Aby pokazać, jak bardzo, wykonaliśmy czynność cztery razy. Widać, że za każdym razem wyniki wyraźnie różnią się od siebie. Jest to bardzo ważne w przypadku funkcji losujących czy też mieszających.

_.size(list)

Funkcja size to niby nic szczególnego. Można by nawet pomyśleć, że ktoś przepisał po prostu znaną wszystkim funkcję length. I co ciekawe, w przypadku kolekcji obiektów lub liczb miałby rację. Ale jak to bywa, diabeł tkwi w szczegółach. Funkcja size umożliwia sprawdzenie liczby właściwości obiektu.

Jako przykład określimy liczbę właściwości obiektu zarówno funkcją size, jak i standardową funkcją length.

Listing 23. Użycie funkcji size i length

var car = {

name: 'BMW',

cost: '10.000$',

year: 2013

};

console.log(car.length);

console.log(_.size(car));

Rysunek 23. Wynik użycia funkcji size i length

Wynik oczywiście jest taki, jak się spodziewaliśmy. Funkcja size jest faktycznie na tyle sprytna, aby zrobić to, czego length nie potrafi, czyli zliczyć liczbę właściwości obiektu.

_.first(array) i _.take(array, n)

Obie wymienione powyżej funkcje służą do pobierania początkowych elementów z tablicy. W pierwszym przypadku jest to pobranie pierwszego elementu, a w drugim - pobranie n początkowych elementów.

Jako przykład zastosowania spróbujemy najpierw pobrać jeden element z tablicy samochodów, a następnie pobierzemy od razu dwa elementy.

Listing 24. Użycie funkcji first oraz take

var cars = [{

name: 'BMW',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

console.log(_.first(cars));

console.log(_.take(cars,1));

console.log(_.take(cars, 2));

Rysunek 24. Wynik użycia funkcji first oraz take

Jak widać, funkcje zachowały się zgodnie z naszymi oczekiwaniami. W pierwszych dwóch przypadkach został nam zwrócony tylko pierwszy element tablicy, a w drugim - dwa początkowe. Faktem, na który warto zwrócić uwagę, jest różnica między pierwszymi dwoma wynikami (first(cars) oraz take(cars,1)). W pierwszym przypadku jest to pojedynczy, czysty obiekt, a w drugim, choć element jest też tylko jeden, został on dodatkowo opakowany w tablicę. O tej właściwości funkcji take warto pamiętać.

_.initial(array, n), _.last(array, n)oraz _.rest(array, index)

Funkcje initial, last i first również pobierają elementy, z tym że ich działania są odniesione do końcowych elementów tablicy. Tak, wiem, może trochę naciągany podział, ale musiałem je jakoś opisać. To może po kolei. Funkcja initial ma dwa parametry. Pierwszym z nich jest tablica, z której będziemy pobierać, drugim zaś liczba określająca, ilu elementów z końca nie można pobrać. Funkcja last działa analogicznie do first, z tym że zwraca n elementów, licząc od końca, a funkcja rest zwraca elementy, licząc od zadanego indeksu.

Do zaprezentowania działania tych funkcji użyjemy tablicy samochodów, a otrzymane wyniki omówimy na podstawie zrzutu z konsoli.

Listing 25. Użycie funkcji initial, last oraz rest

var cars = [{

name: 'BMW',

cost: '10.000$',

year: 2012

}, {

name: 'Audi',

cost: '11.000$',

year: 2013

}, {

name: 'Nissan',

cost: '19.000$',

year: 2010

}, {

name: 'Audi',

cost: '10.500$',

year: 2012

}];

console.log(_.initial(cars, 1));

console.log(_.last(cars, 2));

console.log(_.rest(cars, 2));

Rysunek 25. Wynik użycia funkcji initial, last oraz rest

Wszystkie prezentowane funkcje dały nam takie wyniki, jakich się spodziewaliśmy. W pierwszym przypadku otrzymaliśmy wszystkie elementy tablicy oprócz jednego, ostatniego. W drugim otrzymaliśmy dwa ostatnie elementy, pytając właśnie o dwa ostatnie elementy. W ostatnim również otrzymaliśmy dwa ostatnie elementy tablicy, z tym że pytaliśmy o nie jako o elementy liczone od indeksu 2.

_.compact(array)

W wyniku użycia funkcji compact otrzymamy tablicę, z której zostały usunięte fałszywe wartości. Tak, wiem, że od razu należałoby wyjaśnić, czym są owe fałszywe wartości. Ale może popatrzmy na przykładowy kod (listing 26) i dopiero, analizując wyniki, zobaczymy, co to takiego.

Nasz przykład będzie bardzo prosty. Utworzymy tablicę z najróżniejszymi wartościami, a następnie obejrzymy jej kopię, ale już bez fałszywych wartości.

Listing 26. Użycie funkcji compact

var values = [0, 1, false, 2, ", 3, 'a', true, null, undefined, NaN];

console.log(values);

console.log(_.compact(values));

Rysunek 26. Wynik użycia funkcji compact

Tablica zwrócona przez funkcję compact znacząco różni się od tej wejściowej. Odrzucone zostały następujące wartości: 0, false, pusty ciąg, null, undefined oraz NaN.

_.flatten(array, shallow)

Zadaniem funkcji flatten jest spłaszczenie tablicy. Jako parametr wejściowy przyjmuje ona tablicę, z tym że może ona zawierać bardzo wiele tablic zagnieżdżonych dowolną liczbę razy. Głębokość tablicy nie ma dla funkcji flatten żadnego znaczenia. Drugim parametrem jest wartość logiczna określająca, czy mamy wykonać płytkie wyszukiwanie, czy też nie. Płytkie wyszukiwanie polega na spłaszczeniu elementów tylko z pierwszego poziomu zagłębienia tablicy. Domyślną wartością tego parametru jest false.

W celu zaprezentowania działa funkcji przygotujemy tablicę, a następnie spróbujemy ją spłaszczyć.

Listing 27. Użycie funkcji flatten

var values = ["Value1",

"Value2",

["Value3",

"Value4",

["Value5",

"Value6"

]

]

];

console.log(_.flatten(values));

console.log(_.flatten(values, true));

Rysunek 27. Wynik użycia funkcji flatten

Widzimy, że obie funkcje zachowały się zgodnie z naszymi oczekiwaniami. W pierwszym przypadku otrzymaliśmy całkowicie płaską tablicę zawierającą wylistowane wszystkie elementy tablicy wejściowej. W drugim przypadku wynik jest o wiele ciekawszy. Na pierwszych czterech miejscach faktycznie mamy wypisane wartości tekstowe, na piątym zaś znajduje się tablica, która w kolekcji wejściowej była dwukrotnie zagłębiona.

_.without(array, values)

Funkcja without jest bardzo prosta w swoim zamyśle. Przyjmuje ona dwa parametry. Pierwszym z nich jest tablica wartości, drugim - szereg wartości. W wyniku zwraca ona tablicę bez wartości wymienionych w drugim parametrze.

Nasz przykład również nie będzie bardzo skomplikowany. Utworzymy tablicę, następnie zdefiniujemy wartości elementów, które nas nie interesują, a w wyniku będziemy chcieli otrzymać tablicę już bez tych wartości.

Listing 28. Użycie funkcji without

var values = [1,2,3,4,5,5];

console.log(_.without(values,1,5));

Rysunek 28. Wynik użycia funkcji without

Jak widzimy na na rysunku 28, prawidłowa tablica wynikowa nie zawiera niektórych wartości. W tym miejscu chciałbym zwrócić uwagę na jeden aspekt. W tablicy wejściowej wartość 5 wystąpiła dwa razy. Funkcja without reaguje na wszystkie wystąpienia danej wartości.

_.union(arrays)

Funkcja union ma za zadanie połączenie ze sobą wszystkich tablic wejściowych. Są one łączone na tej samej zasadzie jak w SQL-u, mianowicie zostają zwrócone jedynie wartości unikatowe.

Jako przykład połączymy trzy tablice, które będą zawierały duplikujące się elementy.

Listing 29. Użycie funkcji union

var value1 = [1, 2, 3, 4];

var value2 = [4, 5, 6, 7, 8];

var value3 = [7, 8, 9, 10];

console.log(_.union(value1, value2, value3));

Rysunek 29. Wynik użycia funkcji union

Jak widzimy, funkcja union działa dokładnie tak, jak powiedzieliśmy. Tablice zostały połączone, a pokazane mamy tylko elementy unikalne.

_.intersection(arrays)

Funkcja intersection oblicza iloczyn logiczny listy przekazanych tablic.

W poniższym przykładzie mamy znaleźć iloczyn logiczny trzech tablic zawierających liczby.

Listing 30. Użycie funkcji intersection

var value1 = [1, 2, 3, 4];

var value2 = [1, 4, 6, 7];

var value3 = [1, 2, 3, 4, 5, 6];

console.log(_.intersection(value1, value2, value3));

Rysunek 30. Wynik użycia funkcji intersection

Jak widać na rysunku 30, we wszystkich trzech listach występują 1 oraz 4.

_.difference(array, array)

Funkcja difference jest niemal identyczna z omawianą już funkcją without. Różni się ona tylko drugim parametrem. W tym przypadku jest to tablica zawierająca wartości, których nie chcemy mieć w funkcji wynikowej. Dlatego właśnie nie będziemy przedstawiać tu przykładu. Wierzę, że każdy jest w stanie zamienić dwie cyfry na tablicę zawierającą dwie cyfry.

_object(list, values)

Funkcja ta tworzy obiekt na podstawie dwóch wejściowych tablic. Pierwszą z nich jest kolekcja kluczy, które będzie zawierał dany obiekt. W drugiej tablicy są natomiast wartości, które odpowiednie klucze mają przyjmować.

W naszym przykładzie utworzymy trzy obiekty i postaramy się zaprezentować dokładnie sposób działania omawianej funkcji.

Listing 31. Użycie funkcji object

var keys = ['key1', 'key2'];

var values1 = ['value1'];

var values2 = ['value1', 'value2'];

var values3 = ['value1', 'value2', 'value3'];

console.log(_.object(keys, values1));

console.log(_.object(keys, values2));

console.log(_.object(keys, values3));

Jak widać zasadnicze znaczenie w naszym przykładzie ma liczba elementów w tablicy wartości.

Rysunek 31. Wynik użycia funkcji object

Jak widać na rysunku 31, w wyniku otrzymaliśmy trzy obiekty. Pierwszy z nich powstał, gdy tablica kluczy miała więcej elementów niż tablica wartości. W takiej sytuacji część kluczy przyjmuje wartość undefined. W sytuacji gdy obie tablice mają taką samą liczbę elementów, obiekt zostaje utworzony i jest to scenariusz najbardziej optymistyczny. Ostatni pokazany przypadek ma miejsce, gdy w tablicy wartości jest więcej elementów niż w tablicy kluczy. W takiej sytuacji nadmiarowe wartości są po prostu ignorowane.

_.indexOf(array, value) i _.lastIndexOf(array, value)

Obie funkcje - indexOf i lastIndexOf - służą do pobierania indeksu, na którym znajduje się dany element. Obie też jako pierwszy parametr przyjmują tablicę wartości, a jako drugi - wartość, której indeks chcemy znaleźć. Różnica jest taka, że w pierwszym przypadku zostanie zwrócona pozycja pierwszego napotkanego elementu o wyszukiwanej wartości, w drugim zaś będzie to ostatnia pozycja.

W przykładowym kodzie wykorzystamy w praktyce obie te funkcje i sprawdzimy, co się stanie, gdy szukanej przez nas wartości nie będzie w przeszukiwanej kolekcji.

Listing 32. Użycie funkcji indexOf oraz lastIndexOf

var values = [0, 1, 2, 3, 4, 5, 5];

console.log(_.indexOf(values, 5));

console.log(_.lastIndexOf(values, 5));

console.log(_.indexOf(values, 7));

Rysunek 32. Wynik użycia funkcji indexOf oraz lastIndexOf

Jak widać na rysunku 32, na pierwszych dwóch przykładach wyświetlonych w konsoli oba indeksy elementu o wartości 5 zostały zwrócone prawidłowo. Sytuacja, w której wyszukiwany jest nieistniejący element, też jest obsłużona, można powiedzieć, podręcznikowo. W większości języków, tak jak i tutaj, gdy brakuje elementu w kolekcji, w wyniku otrzymujemy, że znajduje się on na pozycji -1.

_.sortedIndex(list, value)

Patrząc na nazwę funkcji, można by było zgadywać, że najpierw sortuje ona zadaną w pierwszym parametrze kolekcję wejściową, a następnie zwraca indeks wartości z drugiego parametru. Tak jednak nie jest. Ta funkcja, tak jak i kilka wcześniej już omawianych, swym działaniem nie odpowiada w stu procentach temu, czego moglibyśmy się po jej nazwie spodziewać. Funkcja ta zwraca nam miejsce, w którym powinniśmy umieści w tablicy zadaną wartość, tak aby tablica dalej zachowała swój porządek.

Jako przykład zastosowania rozpatrzymy dwa przypadki. Pierwszym z nich będzie sytuacja, gdy tablica jest już uporządkowana, drugim natomiast - gdy jej elementy są nieco pomieszane.

Listing 33. Użycie funkcji sortedIndex

var values1 = [0, 1, 2, 3, 4, 5, 7, 8];

var values2 = [7, 8, 4, 0, 3, 1, 2, 5];

console.log(_.sortedIndex(values1, 6));

console.log(_.sortedIndex(values2, 6));

Rysunek 33. Wynik użycia funkcji sortedIndex

Wynik zwrócony w pierwszym przypadku jest chyba oczywisty. Mówi nam, że gdybyśmy wstawili 6 tam, gdzie znajduje się teraz 7, to lista dalej byłaby uporządkowana. Nie można z tym się nie zgodzić. W drugim przypadku wynik może nie być już taki oczywisty. Spowodowane jest to faktem, że tabela nie była uporządkowana i znalezienie takiego indeksu było niemożliwe. W tej sytuacji funkcja powiedziała nam po prostu, że najlepiej byłoby wstawić element poza tabelą.

_.range(start, stop, step)

Funkcja range powinna być używana, kiedy chcemy wygenerować tabelę z kolejnymi wartościami liczbowymi. Jako pierwszy parametr przyjmuje ona wartość początkową, jako drugi wartość końcową, natomiast trzecim i zarazem ostatnim parametrem jest skok, jaki chcem\y mieć między wartościami.

W celach demonstracyjnych wygenerujemy dwie tabele liczbowe. Pierwsza z nich będzie tworzona przy użyciu jak najprostszych parametrów, druga zaś nie będzie tym, co byśmy może chcieli uzyskać.

Listing 34. Użycie funkcji range

console.log(_.range(-1, 7, 1));

console.log(_.range(-1, 7, -1));

Rysunek 34. Wynik użycia funkcji range

Przyjrzyjmy się najpierw pierwszej tablicy. Widzimy w niej, że faktycznie wartością początkowa jest -1, z tym że warto pamiętać, iż górna granica przekazana w wywołaniu funkcji nie zostanie nigdy osiągnięta. Dlatego właśnie ostatnią wartością w tablicy jest 6. Co do drugiego wywołania, to błąd polegał na tym, że skok był ujemy i 7 nigdy nie zostałoby osiągnięte. W takim przypadku otrzymujemy po prostu pustą tablicę.

_.bind(function, object, arguments)

Funkcja bind służy do przypisywania na stałe wywołania funkcji do danego obiektu. Wiem, że taka definicja może na razie nie mówić zbyt wiele, ale gdy przejdziemy do przykładu, na pewno wszystko okaże się jasne. Najpierw omówmy parametry. Pierwszym z nim jest funkcja, którą chcemy przypisać, drugim obiekt, do którego będzie ona przypisana. Ostatnim parametrem jest dowolna liczba argumentów, które mają być przekazane do wywoływanej funkcji.

Aby poznać w praktyce działanie funkcji bind, utworzymy dwa obiekty oraz funkcję pokazującą informacje zawarte w danym obiekcie. Dodatkowo funkcja będzie zawierać parametr flagi bitowej mówiący, czy mają być pokazane wszystkie informacje, czy tylko część.

Listing 35. Użycie funkcji bind

var audiCar = {

name: "Audi",

year: 1990

};

var bmwCar = {

name: "BMW",

year: 1995

};

var ShowInfo = function (showYear) {

if (showYear) {

console.log(this.name + " from " + this.year);

} else {

console.log(this.name);

}

};

audiCar.ShowInfo = _.bind(ShowInfo, audiCar, true);

bmwCar.ShowInfo = _.bind(ShowInfo, bmwCar);

audiCar.ShowInfo();

bmwCar.ShowInfo();

Rysunek 35. Wynik użycia funkcji bind

Co widzimy w przykładzie na listingu 35? Po pierwsze, mamy zdefiniowane dwa obiekty. Każdy z nich ma dwie właściwości. Dalej mamy zdefiniowaną funkcję, która od razu zostaje przypisana do zmiennej. Zauważmy, że funkcja ta korzysta ze słowa kluczowego this. Będzie się ono odnosić do obiektu, który powiążemy z funkcją. Oprócz tego należy pamiętać, że funkcja ta ma parametr wejściowy. Następnie korzystamy z funkcji bind. Wiążemy funkcję ShowInfo z utworzonymi wcześniej obiektami i przypisujemy ją do nowych właściwości obiektów. W pierwszym przypadku przekazaliśmy jeszcze pozytywną flagę bitową (o wartości 1) mówiącą o tym, że chcemy wyświetlić pełne informacje. Na końcu przykładu widzimy wywołanie z obu obiektów przypisanych do nich przed chwilą funkcji.

Jak widać, otrzymaliśmy oba oczekiwane rezultaty. W pierwszym przypadku widzimy pełne dane z obiektu. Są one pełne, ponieważ przekazaliśmy true jako parametr wejściowy. W drugim przypadku, gdy nie wyraziliśmy pozytywnej decyzji co do pokazania wszystkich danych, otrzymaliśmy tylko dane z pola name. Zauważmy, że w obu przypadkach funkcja poprawnie identyfikowała obiekt, który ją wywołuje.

_.partial(function, arguments)

Funkcja partial umożliwia częściowe zdefiniowanie parametrów wejściowych funkcji. Jej pierwszym argumentem jest funkcja, której parametry chcemy definiować, kolejnymi są wartości, które chcemy przekazać do funkcji wejściowej. Jako wynik otrzymujemy kopię funkcji, która już ma zdefiniowane część parametrów wejściowych.

W tym przypadku jako przykład wykorzystania przedstawimy funkcję łączącą ze sobą dwa ciągi znaków. Spróbujemy jednak za pomocą funkcji partial utworzyć kopię tej funkcji ze zdefiniowanym pierwszym parametrem.

Listing 36. Użycie funkcji partial

var exampleFunction = function(first, second){

console.log(first + "_" + second);

};

var partialExample = _.partial(exampleFunction, 'Number one');

partialExample('Number Two');

partialExample('Number Three');

Rysunek 36. Przykład użycia funkcji partial

Jak widzimy na rysunku 36, w obu przypadkach na pierwszym miejscu jest pokazany predefiniowany parametr Number one. Na drugim zaś jest już ten, który został przekazany bezpośrednio przy wywoływaniu funkcji.

_.memoize(function)

Funkcja memoize umożliwia przechowanie w pamięci podręcznej wywołań funkcji podanej jako jej parametr. Dzięki temu, jeżeli będziemy chcieli wywołać tę funkcję po raz kolejny z tymi samymi parametrami, nie zostanie ona faktycznie wywołana, a jedynie otrzymamy zapamiętany wynik jej wykonania.

Jako przykład utworzymy funkcję dodającą dwie wartości. Będzie ona również wypisywać w konsoli wyniki każdego swojego wykonania. Uruchomimy ją w dwóch identycznych pętlach i zobaczymy, czy wyniki faktycznie zostały zapamiętane.

Listing 37. Użycie funkcji memoize

var addNumbers = function (a, b) {

console.log("Adding " + a + " + " + b);

return a + b;

};

var memoizedFunction = _.memoize(addNumbers);

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

console.log("Equels " + memoizedFunction(i, i));

}

console.log("------------------------------");

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

console.log("Equels " + memoizedFunction(i, i));

}

Rysunek 37. Wynik użycia funkcji memoize

Jak widać, w pierwszej pętli funkcja faktycznie zostaje wywołana. Widzimy to po wpisie dotyczącym dodawania. Warto zauważyć, że druga pętla nie wymaga już wywołań funkcji. Korzysta ona od razu z zapamiętanego wyniku.

_.delay(function, wait, arguments)

Funkcja delay oferuje niemal identyczne funkcjonalności jak setTimeout. Różnią się one sposobem wywołania oraz argumentami wejściowymi. W przypadku delay pierwszym argumentem jest funkcja, którą chcemy wywołać. Następnym jest czas w milisekundach, który należy odczekać. Na końcu należy podać argumenty, które mają być przekazane do funkcji będącej pierwszym parametrem.

Zaprezentujemy działanie metody delay na podstawie funkcji wypisującej w konsoli przekazany parametr.

Listing 38. Użycie funkcji delay

var showInfo = function (info) {

console.log("Show Info function");

console.log(info);

console.log(new Date().getTime() + " from function");

};

_.delay(showInfo, 2500, "My info");

console.log(new Date().getTime() + " Main");

Rysunek 38. Wynik użycia funkcji delay

Na początku w zaprezentowanym kodzie widzimy definicję funkcji, która wypisuje w konsoli zadany parametr oraz dodaje stempel czasowy zaraz potem. Następnie wywołanie tej funkcji zostaje opóźnione o 2,5 sekundy. Ostatnim wierszu jest zapis stempla czasowego chwilę po zadeklarowaniu chęci opóźnienia funkcji.

Przyjrzymy się dokładnie temu, co widać w konsoli. W pierwszym wierszu jest informacja, która została zalogowana jako pierwsza - jest to stempel z głównego wątku aplikacji. Dopiero później, po mniej więcej 2,5 sekundy, opóźniona funkcja faktycznie zostaje wywołana.

_.defer(function, arguments)

Funkcja defer pozwala na wywołanie pewnej porcji kodu dopiero wtedy, gdy stos wywołań (call stack) będzie pusty, czyli w momencie, gdy nie będzie już żadnej innej operacji do wykonania. W tej metodzie jako pierwszy parametr przekazujemy funkcję, która ma być wywołana, a jako drugi (tu opcjonalny) możemy przekazać jej parametry wejściowe.

Jako przykład będziemy chcieli wypisać w konsoli informację mówiącą o tym, że wszystkie inne operacje już się wykonały.

Listing 39. Użycie funkcji defer

var showInfo = function (info) {

console.log("Show Info function");

console.log(info);

};

_.defer(function () {

console.log("Done");

});

showInfo("MyInfo");

Rysunek 39. Wynik użycia funkcji defer

Jak widać (listing 39), chcemy otrzymać napis Done, gdy pozostałe funkcje naszego programu zostaną zakończone. Jako pozostałe rozumiemy tutaj funkcję do wyświetlania informacji.

W naszej konsoli faktycznie widzimy, że w wyniku wykonania funkcji defer napis o zakończeniu działania pojawił się dopiero wtedy, gdy wszystkie inne akcje dobiegły końca.

_.throttle(function, wait)

Funkcja throttle umożliwia zapewnienie, że pewna funkcja w danym przedziale czasu zostanie wywołana tylko jedne raz. Jej pierwszym argumentem jest funkcja, na którą chcemy nałożyć to ograniczenie czasowe, a drugim - czas, jaki musi upłynąć, by można było po raz kolejny uruchomić tę funkcję.

Do celów demonstracyjnych utworzymy funkcję, której jedynym zadaniem będzie wypisywanie na konsoli przekazanego parametru. Następnie przy użyciu metody throttle utworzymy jej kopię, która nie będzie mogła być uruchamia częściej niż co 100 milisekund. Na koniec dla potwierdzenia tego faktu wywołamy ją 900 000 razy.

Listing 40. Użycie funkcji throttle

var throttleExample = function (i) {

console.log("value = " + i);

};

var throttleFunction = _.throttle(throttleExample, 100);

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

throttleFunction(i);

}

Rysunek 40. Wynik użycia funkcji throttle

Widzimy, że choć wywołaliśmy funkcję prawie milion razy, faktycznie wykonała się one jedynie pięć razy - ograniczenie uruchamiania raz na 100 milisekund zadziałało.

_.debounce(function, wait)

Ta funkcja również służy od opóźnienia faktycznego wywołania jakiejś funkcji. Robi to jednak nieco inaczej niż ta omówiona w poprzednim punkcie, choć przyjmuje takie same parametry. W tym przypadku podany czas określa, ile należy odczekać od ostatniego wywołania funkcji, aby ponownie została ona wywołana.

Najbardziej naturalnym i najczęstszym zastosowaniem funkcji debounce jest jej użycie w przypadku przetwarzania napływających danych wejściowch. Ma to miejsce na przykład wtedy, gdy użytkownik wpisuje dane w pole tekstowe, a nie chcemy, aby operacja na nich wykonywała się co chwilę, lecz jakiś czas po zaprzestaniu pisania.

Jako przykład posłuży nam nieco zmodyfikowany kod z listingu 40. Będziemy cyklicznie wywoływać funkcję i zobaczymy, czy faktycznie wywoła się ona dopiero jakiś czas po ostatnim odwołaniu.

Listing 41. Użycie funkcji debounce

var debounceExample = function (i) {

console.log("value = " + i);

console.log(new Date().getTime());

};

var debounceFunction = _.debounce(debounceExample, 2000);

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

if (i === 899) {

console.log(new Date().getTime());

}

debounceFunction(i);

}

Rysunek 41. Wynik użycia funkcji debounce

Warto zwrócić uwagę, że teraz funkcja musi odczekać aż dwie sekundy od ostatniego wywołania, aby faktycznie się wykonać, oraz że pętle wywołuje się tylko 900 razy.

Widzimy, że faktycznie funkcja wykonała się dopiero 2 sekundy po ostatnim wywołaniu. Jest to technika bardzo przydatna szczególnie wtedy, gdy mamy dane napływające regularnie, a działanie musimy podjąć dopiero wtedy, gdy spłyną już wszystkie.

_.once(function) i _.after(counter, function)

Obie metody służą do ustalenia, czy funkcja ma być wykonana w zależności od liczby jej wywołań. W przypadku once nie trzeba chyba zbyt wiele wyjaśniać - kopia przekazanej funkcji ma zostać wywołana tylko raz. Użycie after spowoduje, że funkcja uruchomi się, jeżeli jest to n-te lub kolejne wywołanie. Jest ona bardzo przydatna w sytuacji, kiedy mamy kilka odwołań asynchronicznych i chcemy wywołać funkcję dopiero wtedy, gdy wszystkie zostaną zakończone.

Listing 42. Przykład użycia funkcji once oraz after

var showFunction = function (val) {

console.log(val);

};

var onceExample = _.once(showFunction);

var afterExample = _.after(4, showFunction);

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

onceExample("Once" + i);

afterExample("After" + i);

}

Rysunek 42. Wynik użycia funkcji once oraz after

W powyższym przykładzie są obie wspomniane funkcje. Pierwsza z nich ma zadziałać, jeżeli jest to jej pierwsze wywołanie, druga zaś - jeżeli była wcześniej wywoływana przynajmniej 3 razy.

Jak widać na rysunku 42, dzięki funkcjom z biblioteki Underscore.js w bardzo prosty sposób możemy kontrolować liczbę wykonań danej funkcji.

_.wrap(function, wrapper)

Funkcja ma dwa parametry. Pierwszym jest funkcja wewnętrzna, a drugim - funkcja owijająca. I teraz w funkcji, którą otrzymamy z funkcji wrap, pierwsza funkcja wejściowa będzie przekazywana automatycznie jako pierwszy parametr wejściowy do drugiej funkcji przekazanej do funkcji wrap. Wiem strasznie to zagmatywane, ale zerknijmy na przykład kodu, a wszystko stanie się jasne.

Listing 43. Użycie funkcji wrap

var insideFunction = function (val) {

console.log("insideFunction");

console.log(val);

};

var outsideFunction = function (funct, val) {

funct(val);

console.log("outsideFunction");

console.log(val);

};

var wrapedFunction = _.wrap(insideFunction, outsideFunction);

wrapedFunction("Wiadomosc do wyslania");

Początkowe cztery wiersze naszego kodu to funkcja insideFunction przyjmująca jeden parametr i wypisująca go na konsoli. Następnie mamy funkcję outsideFunction, której pierwszym parametrem jest funkcja funct, a drugim, powiedzmy, wartość tekstowa. Następnie wywołuje ona funkcję z pierwszego parametru, z przekazanym drugim parametrem. W przedostatnim wierszu jest wywołanie wrap. Zwraca ona kopię funkcji outsideFunction, która to ma na stałe określony jako pierwszy parametr funkcję insideFunction. W ostatnim wierszu jest wywołanie nowo powstałej funkcji wraz z wiadomością, która chcemy wysłać.

Rysunek 43. Wyniku użycia funkcji wrap

Jak widać, funkcja wrap prawidłowo opakowała jedną funkcję w drugą.

_.compose(functions)

Funkcja compose służy do tworzenia funkcji złożonych. Jako kolejne parametry wejściowe przyjmuje ona funkcje, z których będziemy tworzyć naszą funkcję złożoną.

Pojęcie funkcji złożonej jest znane z matematyki. Najprościej można to wytłumaczyć następująco: jest to umieszczenie jednej funkcji wewnątrz drugiej, tak że wynik pierwszej staje się argumentem wejściowym drugiej. Wzór można zapisać na przykład tak:

h (x) = g ( f (x))

Listing 44. Użycie funkcji compose

var A = function (val) {

return 'A(' + val + ')';

};

var B = function (val) {

return 'B(' + val + ')';

};

var C = function (val) {

return 'C(' + val + ')';

};

var composedFunction = _.compose(A, B, C);

console.log(composedFunction('Przykladowy tekst'));

Na początku są zadeklarowane trzy funkcje, A, B i C, Każda z nich zwraca przekazaną wartość ujętą w nawiasy poprzedzone swoją nazwą. W przedostatnim wierszu jest wywołanie funkcji compose w celu utworzenia funkcji złożonej composedFunction poprzez opakowanie funkcji C funkcją B oraz funkcji B funkcją A. W ostatnim wierszu jest wywołanie funkcji złożonej composedFunction z przykładowym tekstem.

Rysunek 44. Wynik użycia funkcji compose

Na rysunku 44 widać, która wartość była przekazywana do której funkcji - za pomocą funkcji compose faktycznie możemy więc utworzyć funkcję złożoną.

_.chain(object) i _(object).value()

Omawianie działania tych funkcji zacznijmy od chain. Umożliwia ona wywoływanie na danym obiekcie kolejno metod jedna po drugiej i za każdym razem wynik jednej metody będzie przekazywany jako argument do kolejnej. Dokładnie tak jak ma to miejsce w jQuery. Tak naprawdę to funkcja chain opakowuje zadany obiekt, tak aby opisane powyżej zachowanie było możliwe. I tu pojawia się funkcja value. Można myśleć o niej w ten sposób, że odpakowuje ona obiekt zapakowany przez chain i zwraca jego wartość.

Jako przykład za pomocą dwóch prezentowanych funkcji w jednym ciągu wywołań znajdziemy najstarszy z samochodów ze znanej nam już tablicy.

Listing 45. Użycie funkcji chain oraz value

var cars = [{

name: "BWM",

cost: "10.000$",

year: 2012

}, {

name: "Audi",

cost: "11.000$",

year: 2013

}, {

name: "Nissan",

cost: "19.000$",

year: 2010

}, {

name: "Audi",

cost: "10.500$",

year: 2012

}];

var chainedCars = _.chain(cars);

var oldestCar = chainedCars.sortBy('year')

.first()

.value();

console.log(oldestCar);

Początkowy fragment kodu to deklaracja kolekcji samochodów. Obiekt kolekcji jest potem opakowywany, tak aby dało się na nim kolejno wywoływać metody. Następnym krokiem jest wywołanie trzech metod na tej nowo powstałej kolekcji, przy czym warto zauważyć, że ostatnia z tych metod odpakowuje go. Na końcu oczywiście zapisujemy wyniki naszego działania.

Rysunek 45. Wynik użycia funkcji chain oraz value

Na rysunku 45 widać , że funkcja chain faktycznie umożliwia nam wykonanie operacji bardzo podobnych do tych, które znamy z jQuery.

_.tap(object, interceptor)

Funkcja tap jest bardzo mocno powiązana z omawianą przed chwilą funkcją chain. Służy ona do modyfikowania obiektów, na których przeprowadzamy operacje łańcuchowe. Jako parametr przyjmuje ona funkcję, która będzie wywoływana dla każdego obiektu w kolekcji, na której przeprowadzamy operację. Funkcja ta jako parametr przyjmuje właśnie kolejno obiekty z wyżej wymienionej kolekcji.

Do celów demonstracyjnych posłuży nam poprzedni przykład. Z tym że zmodyfikujemy go tak, aby do tego najstarszego samochodu dodał pole jednoznacznie informujące o tym, że to właśnie on jest najstarszy.

Listing 46. Użycie funkcji tap

var cars = [{

name: "BMW",

cost: "10.000$",

year: 2012

}, {

name: "Audi",

cost: "11.000$",

year: 2013

}, {

name: "Nissan",

cost: "19.000$",

year: 2010

}, {

name: "Audi",

cost: "10.500$",

year: 2012

}];

var chainedCars = _.chain(cars);

var oldestCar = chainedCars.sortBy('year')

.first()

.tap(function (car) {

car.Info = "Oldest one"

})

.value();

console.log(oldestCar);

Rysunek 46. Wynik użycia funkcji tap

Jak widzimy, funkcja tap zadziałała zgodnie z naszymi oczekiwaniami. Efekt jej działania jest widoczny na obiekcie najstarszego samochodu. Obiekt ten został rozszerzony o pole, którego nie miał w swoje pierwotnej definicji.

_.keys(object), _.values(object), _.pairs(object) oraz _.invert(object)

Wszystkie powyższe funkcje znalazły się w tym miejscu, ponieważ wykonują one działania na parze klucz-wartość. Pierwsza z nich zwraca wszystkie klucze występujące w obiekcie, a druga - wszystkie wartości ze wspomnianej przed chwilą pary. Funkcja pairs natomiast zwraca nam tablicę obiektów mającą odpowiednio po dwie wartości pasujące do współczynników klucz-wartość. Ostatnią z przedstawionych funkcji jest invert. Bardzo łatwo jest się domyśleć, jaki może być wynik działania tej funkcji. Mianowicie zwraca ona kopię obiektu z zamienionymi miejscami w parach elementami klucz-obiekt.

Jako przykład utworzymy prosty obiekt i zastosujemy na nim każdą z wymienionych funkcji.

Listing 47. Użycie funkcji keys, values, pairs oraz invert

var car = {

name: "Audi",

cost: "10500",

year: "2012"

};

console.log("Keys_" + _.keys(car));

console.log("Values_" + _.values(car));

console.log("Pairs");

console.log(_.pairs(car));

console.log("Invert");

console.log(_.invert(car));

Rysunek 47. Wynik użycia funkcji keys, values, pairs oraz invert

Wydaje mi się, że wyniki zaprezentowane na rysunku 47 nie wymagają komentarza ani tłumaczenia.

_.functions(object)

Funkcja functions zwraca wszystkie metody danego obiektu przekazanego w parametrze wejściowym.

Jako przykład wypiszemy wszystkie metody oferowane nam przez omawianą bibliotekę Underscore.js.

Listing 48. Użycie funkcji functions

var underscoreFunctions = .functions();

console.log("Underscore functions are: " + underscoreFunctions);

Rysunek 48. Wynik użycia funkcji functions

Za pomocą tego bardzo prostego fragmentu kodu, w którym wykorzystujemy funkcję functions, udało nam się wypisać wszystkie metody z biblioteki Underscore.js. Można ją oczywiście zastosować do każdego obiektu, na którym pracujemy.

_.extend(destination, sources), _.pick(object, keys) oraz _.omit(object, keys)

Wszystkie przedstawione powyżej funkcje służą do utworzenia obiektu na podstawie innych obiektów. W przypadku extend należy podać przynajmniej dwa obiekty. Pierwszy z nich zostanie rozszerzony o pola znajdujące się w kolejnych parametrach. Funkcja pick również jako pierwszy parametr przyjmuje obiekt, kolejnymi są nazwy kluczy, które mają się znaleźć w wynikowym obiekcie. Funkcji omit działa niemal identycznie jak pick, z tym że podajemy nie listę kluczy, które mają się znaleźć w nowym obiekcie, lecz te, których chcemy się pozbyć.

Przykład ilustruje działanie trzech omawianych funkcji - w wyniku wykonania kodu z listingu 49 powstają trzy obiekty. Może to nic specjalnego, ale na pewno uzmysłowi nam to dokładnie, jak każda z nich działa.

Listing 49. Użycie funkcji extend, pick oraz omit

var object1 = {

key1: "value1",

key2: "value2",

key3: "value3"

};

var object2 = {

key4: "value4",

key5: "value5"

};

var extendedObject = _.extend(object1, object2);

var pickedObject = _.pick(extendedObject, "key1", "key5");

var omitedObject = _.omit(extendedObject, "key1", "key5");

console.log("Extended");

console.log(extendedObject);

console.log("Picked");

console.log(pickedObject);

console.log("Omited");

console.log(omitedObject);

Rysunek 49. Wynik użycia funkcji extend, pick oraz omit

Jak widać na rysunku 49, wszystkie funkcje zachowały się tak, jak tego oczekiwaliśmy. W pierwszym przypadku pierwszy obiekt faktycznie został rozszerzony o wszystkie pola, które zawierał drugi obiekt. Warto wspomnieć, że gdyby nazwy pól się powtarzały, w obiekcie wynikowym byłyby te z drugiego obiektu. W drugim i trzecim przypadku faktycznie, opierając się na pasujących do siebie nazwach pól, udało nam się wymusić, jaki ma być obiekt zwrócony przez funkcję.

_.clone(object)

Funkcja clone służy do tworzenia płytkich kopii obiektu. Jej jedynym parametrem jest obiekt, którego kopię chcemy uzyskać. Do wyjaśnienia pozostaje, co znaczy "płytkie klonowanie". Otóż, jeśli klonowane są wartości obiektu, który zawiera jakieś typy złożone, to wartości te także po kopiowaniu odnoszą się do tego samego miejsca. Najlepiej oczywiście będzie zobaczyć to na przykładzie.

Kod z listingu 50 realizuje klonowanie obiektu. Na przykładzie oryginalnego obiektu oraz tego, który jest klonem, pokażę, na co należy uważać, stosując funkcję clone.

Listing 50. Użycie funkcji clone

var originalObject = {

simpleValue: 'Value1',

arrayValue: ['A1', 'A2']

};

var clonedObject = _.clone(originalObject);

clonedObject.simpleValue = 'Value2';

clonedObject.arrayValue.push('A3');

console.log("clonedObject");

console.log(clonedObject);

console.log("originalObject");

console.log(originalObject);

Rysunek 50. Wynik użycia funkcji clone

Na samym początku deklarujemy obiekt, który ma dwa pola. W pierwszym polu jest przechowywany ciąg znaków, a drugie zawiera odniesienie do tablicy. Obiekt ten zostaje sklonowany, a następnie w jego klonie pola zostają zmienione. Zobaczmy, co ostatecznie otrzymamy.

Patrząc na oba wypisane obiekty, warto zwrócić uwagę, czym się one różnią, a co jest w nich takie same. Po pierwsze pole simpleValue zostało zmienione w sklonowanym obiekcie i zmiana ta nie miała wpływu na obiekt oryginalny. Inaczej natomiast sytuacja ma się, jeżeli chodzi o wartość tabelaryczną. Zauważmy, że zmiana przeprowadzona na niej w obiekcie sklonowanym miała wpływ na to, jak to samo pole wygląda w obiekcie oryginalnym. Wynika to oczywiście z faktu, że obiekty te były klonowane płytko.

_.has(object, key)

Funkcja has ma za zadanie pokazanie, czy obiekt zawiera daną właściwość. Jako pierwszy parametr przyjmuje ona obiekt, a jako drugi - nazwę klucza, którego istnienie w obiekcie chcemy zweryfikować.

Jako przykład utworzymy obiekt i na nim sprawdzimy działanie omawianej funkcji.

Listing 51. Użycie funkcji has

var exampleObject = {

key1: 'value1',

key2: undefined

};

console.log('Has key1');

console.log(_.has(exampleObject, 'key1'));

console.log('Has key2');

console.log(_.has(exampleObject, 'key2'));

console.log('Has key3');

console.log(_.has(exampleObject, 'key3'));

Rysunek 51. Wynik użycia funkcji has

W naszym przykładzie na początku utworzyliśmy obiekt z dwoma polami. W pierwszym z nich jest ciąg znaków, a drugie przyjęliśmy jako niezdefiniowane. Zobaczmy, jak funkcja has zachowa się w tej sytuacji.

W konsoli widzimy trzy wyniki. Pierwszy i ostatni nie są żadnym zaskoczeniem. Natomiast chciałbym zwrócić uwagę na drugi wynik. Warto zapamiętać, że nawet gdy pole jest ustawione jako niezdefiniowane, funkcja potwierdzi jego istnienie w obiekcie.

_.isEqual(object1, object2)

Funkcja isEqual służy do przeprowadzenia głębokiego porównania dwóch obiektów. Głębokie porównanie jest to sprawdzanie, czy faktycznie we wszystkich polach obiektu znajdują się takie same wartości.

Jako przykład użycia utworzymy dwa na pozór identyczne obiekty i porównamy je na trzy sposoby. Na podstawie wyników tych porównań będzie można stwierdzić, czym różni się głębokie porównanie od płytkiego.

Listing 52. Użycie funkcji isEqual

var firstObject = {

key1: 'value1',

key2: [

'value2', 'value2']

};

var secondObject = {

key1: 'value1',

key2: [

'value2', 'value2']

};

console.log('==');

console.log(firstObject == secondObject);

console.log('===');

console.log(firstObject === secondObject);

console.log('_.isEqual');

console.log(_.isEqual(firstObject, secondObject));

Rysunek 52. Wynik użycia funkcji isEqual

Nasz przykład opiera się na dwóch obiektach, które tworzymy na samym początku. Na pierwszy rzut oka są one identyczne. Sęk jednak w tym, że w jednym z pól jest tablica, która jest traktowana jak obiekt. Następnie porównujemy te dwa obiekty na trzy różne sposoby.

Na rysunku 52 widzimy, że dwa pierwsze porównania mówią nam, że obiekty nie są sobie równe. Wynika to z faktu, że w przypadku obiektu, jakim jest tablica wartości, wykonując płytkie porównanie, nie sprawdzimy faktycznej wartości jej elementów, lecz jedynie to, czy odnosi się ona do tego samego miejsca w pamięci. Inaczej natomiast wygląda sytuacja, jeżeli chodzi o wynik porównania przy użyciu funkcji isEqual. Funkcja ta dokonuje głębokiego porównania, czyli sprawdza faktyczne wartości porównywanych elementów.

_.is(na sto sposobów)

Biblioteka Underscore.js oferuje nam cały zestaw użytecznych funkcji, które zaczynają się od słowa is. Wszystkie te funkcje pozwalają nam sprawdzić, czy przekazane argumenty spełniają jakiś warunek. W czasie pisania tego tekstu było ich piętnaście. Aby zobaczyć dokładnie, jakiego rodzaju warunki można za ich pomocą sprawdzić, wystarczy zajrzeć do dokumentacji. Są one na tyle intuicyjne, że nie będę tutaj przedstawiać przykładów ich wykonania.

_.noConflict()

Praktyka tworzenia funkcji służących do przeciwdziałania konfliktom jest powszechna w przypadku tworzenia bibliotek JavaScriptu. Problem z konfliktami polega na tym, że kilka bibliotek może korzystać z tego samego symbolu. W przypadku jQuery był to $, a w Underscore jest to _.

Ponieważ jest to książka dla osób znających już jQuery, wierzę, że żaden przykład nie jest potrzebny i opis zawarty w dokumentacji wystarczy do tego, aby zrozumieć działanie funkcji noConflict.

_.times(n, iterator) i _.random(min, max)

Funkcja times umożliwia wywołanie pewnego kodu zadaną liczbę razy. W pewnym sensie działa to podobnie do pętli, gdzie wymuszamy wykonanie czegoś na przykład osiem razy. Drugą omawianą tutaj funkcją jest random. Pozwala ona, jak można się domyśleć, na wygenerowanie losowej liczby z wybranego przedziału.

Jako przykład użycia tych dwóch funkcji wykonamy pięć razy kod zapisujący w konsoli losową liczbę z przedziału od jednego do stu.

Listing 53. Użycie funkcji times oraz random

_(5).times(function (n) {

console.log(n + ' time');

console.log('Random value: ' + _.random(1, 100));

});

Rysunek 53. Wynik użycia funkcji times oraz random

Jak widać, po pierwsze, funkcja faktycznie wywołała się pięć razy, a po drugie, możemy bez problemu przekazać do funkcji numer jej wywołania. Ostatnim natomiast faktem jest to, że funkcja losująca działa poprawnie i wartości nie ulegają powtórzeniu.

_.mixin(object)

Przy użyciu funkcji mixin możemy rozszerzać bibliotekę Underscore.js o nasze własne funkcje. Parametrem przyjmowanym przez tę funkcję jest odpowiednio sformatowany obiekt zawierający przygotowane przez nas metody.

Dla nikogo nie będzie pewnie zaskoczeniem, że przykład z listingu 54 realizuje rozszerzanie biblioteki o dodatkową funkcję.

Listing 54. Użycie funkcji mixin

_.mixin({

exampleFunction: function(arg1){

console.log("From my function: " + arg1);

}

});

_.exampleFunction('Example value');

W przykładowym kodzie warto zwrócić uwagę, jak wygląda obiekt przekazany jako argument. Od jego budowy zależy poprawne wykonanie funkcji mixin.

Rysunek 54. Wynik użycia funkcji mixin

Jak widać na rysunku 54, operacja dodawania nowej funkcji zakończyła się sukcesem. Mając jedynie funkcje dostępne w bibliotece, nie moglibyśmy przecież pomyślnie wywołać funkcji exampleFunction.

_.unique(prefix)

Funkcja unique jest bardzo przydatna, jeżeli nasz kod ma dość mocno ingerować w budowę strony. Chodzi tu głównie o dodawanie nowych elementów. Dzięki funkcji unique możemy otrzymać unikalny identyfikator dla nowo tworzonego obiektu. Przydatności takiego rozwiązana chyba nie ma nawet potrzeby reklamować. Jako jedyny argument wejściowy przekazujemy prefiks identyfikatora nowego obiektu na przykład authorLabel_.

_.escape(text) i _.unescape(text)

Obie omawiane w tym punkcie funkcje służą odpowiednio do kodowania i dekodowania tekstu otrzymanego w wejściowym parametrze. Takiego typu zabiegi są potrzebne, ponieważ nigdy nie możemy w pełni ufać ciągom znaków otrzymanym od użytkownika. Nie możemy przecież pozwolić na uruchomienie na naszej stronie jakieś skryptu napisanego przez kogoś obcego.

Jako przykład wykonania najpierw zakodujemy, a potem odkodujemy tekst zawierający znaki, których nie chcielibyśmy przekazywać bezpośrednio na naszą stronę.

Listing 55. Użycie funkcji escape oraz unescape

var exampleText = '< & jakiś tekst & >';

console.log(_.escape(exampleText));

console.log(_.unescape(_.escape(exampleText)));

Rysunek 55. Wynik użycia funkcji escape oraz unescape

Jak widać, udało nam się zarówno zakodować, jak i odkodować zadany tekst.

_.result(object, property)

Zanim zacznę opisywać tę funkcję, chciałbym powiedzieć, że staram się z niej nie korzystać i wam też to radzę. Dlaczego tak uważam, powiem na końcu opisu, a na razie powiedzmy sobie, jak ona działa. Być może podczas omawiania ktoś jeszcze dojdzie do takiego samego wniosku jak ja.

Omawiana metoda ma dwa parametry. Pierwszym z nich jest obiekt, drugim zaś nazwa właściwości lub funkcji z tego obiektu. Jako wynik result otrzymujemy albo wartość tej właściwości, albo wynik wykonania tej funkcji.

Jako przykład utworzymy obiekt, który będzie miał zarówno pole właściwości, jak i funkcji. Następnie odwołamy się do tych dwóch elementów za pomocą result.

Listing 56. Użycie funkcji result

var exampleObject = {

prop1: "value of prop1",

func1: function () {

return "value of func1";

}

};

console.log("Prop1 result:");

console.log(_.result(exampleObject, "prop1"));

console.log("Func1 result:");

console.log(_.result(exampleObject, "func1"));

console.log("Prop2 result:");

console.log(_.result(exampleObject, "prop2"));

Rysunek 56. Wynik użycia funkcji result

Jak widać na rysunku 56, w pierwszych dwóch przypadkach zarówno odwołanie do właściwości, jak i wywołanie funkcji zostało zrealizowane poprawnie. Tak samo jak wtedy, gdy chcemy odwołać się do nieistniejącego pola. Funkcja result jest zaimplementowana jak najbardziej poprawnie i moja niechęć do niej wynika z czegoś zupełnie innego. Uważam mianowicie, że funkcja ta zachęca nieco do bałaganu w kodzie. Jeżeli już coś piszemy, to chyba można na tyle utrzymywać porządek, aby wiedzieć, czy coś jest funkcją, czy właściwością.

_.template()

Muszę przyznać, że poświęciłem trochę czasu, zastanawiając się nad tym, czy pisać o tej funkcji, czy też nie. Decyzja ostatecznie padła na "nie". Już tłumaczę, dlaczego. Bibliotek do szablonów jest bardzo dużo. Moja ulubiona jest pod adresem http://handlebarsjs.com/. Chodzi mi o to, że jeżeli mamy się już czegoś uczuć, nauczmy się tego na rozwiązaniu, które jest większe i przystosowane do tego, co chcemy robić. Nie mówię w tym momencie, żeby nie uczyć się Underscore.js. Wręcz przeciwnie, uważam, że warto się go nauczyć z tego źródła czy jakiegokolwiek innego. Po prostu chcę powiedzieć, że nie jest to rozwiązanie do szablonów i można bez problemu znaleźć coś lepszego.

Podsumowanie

Chciałbym przeprosić, jeżeli ktoś poczuł się znudzony omawianiem kolejnych funkcji i przykładów. Proszę uwierzyć, że warto je poznać. Biblioteka Underscore.js nie jest jakimś potężnych narzędziem, które zrobi masę rzeczy za nas. Ma ona służyć zupełnie do czegoś innego. Ma dostarczyć użytecznych metod, które pomogą nam w codziennej pracy bez względu na to, nad jakim rozwiązaniem pracujemy.

Jeśli prześledziłeś skrupulatnie przykłady, to jestem pewien, że zyskałeś dzięki temu sporo przydatnej wiedzy. Lecz jeśli w pewnym momencie przeskoczyłeś do podsuwania (sam czasami też tak robię), to zachęcam, aby mimo wszystko rzetelnie przejrzeć cały materiał. Obiecuję, że warto.

Wprowadzenie

Zanim przejdę do szczegółów i powiem, co znajdziemy w książce, chciałbym wyjaśnić parę kwestii. Po pierwsze i najważniejsze - nie jest to dokumentacja przedstawianych rozwiązań ani kolejna książka zawierająca wszystkie istniejące informacje na ich temat, omawiająca krok po kroku każdy ich aspekt. Osobiście nigdy tego typu książek nie lubiłem i na pewno nie zamierzałem napisać niczego takiego. Zamiast tego starałem się napisać książkę możliwie przystępnym językiem. Chciałbym, aby Czytelnik odniósł wrażenie, że siedzimy obok siebie, pracując nad jakimś projektem, i rozmawiamy, w jaki sposób go tworzyć. Poznajemy różne technologie i narzędzia, a na samym końcu oczywiście staramy się zaimplementować coś, co będzie stanowiło całość. Dlatego właśnie, jeżeli poszukujecie kolejnego tomu podobnego do dziesiątek książek informatycznych dostępnych na rynku, możecie śmiało tę książkę odłożyć i wrócić do tych dziesiątek. Jeżeli natomiast jesteście już znużeni tym, że w gruncie rzeczy większość książek największych wydawnictw jest praktycznie taka sama, to zapraszam do lektury.

Dla kogo jest ta ta książka? Odpowiedź na to pytanie powinna pojawić się na początku każdego typu publikacji. Super chwytem marketingowym byłoby napisanie czegoś w stylu: "To książka przeznaczona dla wszystkich pasjonatów technologii internetowych, którzy chcą pogłębić swoją wiedzę". Świetne hasło reklamowe, prawda? Ale nie o takie populistyczne ogólniki mi chodzi. Dlatego właśnie, pisząc o osobach, które chciałbym, aby ją przeczytały, postaram się być maksymalnie szczery. Nie jest ona z pewnością przeznaczona dla osób, które dopiero zaczynają tworzyć rozwiązania oparte na języku JavaScript, ponieważ, aby prześledzić podane w niej przykłady, trzeba znać podstawy programowania w tym języku. Nie mówię oczywiście, żeby jej nie kupować. Kupcie. Odłóżcie na półkę i wróćcie do niej, gdy będziecie gotowi. A tak poważnie, to jest ona przeznaczona dla osób, które poznały już dość dobrze podstawy języka JavaScript i oczywiście, co za tym idzie, także jQuery. Tak wiem, że to nie jest to samo i że z punktu widzenia wielu osób strasznie upraszczam w tym momencie. Odpowiedzcie sobie jednak na pytanie: Czy te dwie technologie nie są teraz traktowane przez większość osób jak jedna nierozłączna całość? W tym właśnie momencie wyłania się opis grupy osób, do których chciałem skierować tę książkę. Mianowicie, chodzi mi o osoby, które mają już powoli dość pisania aplikacji, wykorzystując tylko te dwa rozwiązania, i chciałyby poszerzyć swoje umiejętności o technologie i narzędzia pokrewne. Tak jak wspomniałem, większości rozwiązań nie omówię od A do Z, bo powstałby kolejny podręcznik użytkownika, w żargonie zwany manualem. Opiszę je na tyle dokładnie, aby można było się w nich dość swobodnie poruszać. W większości przypadków będę je demonstrował na przykładzie tworzonego projektu. Uważam, że jest to bardzo ważne, gdyż najtrudniej jest zacząć. A gdy komuś przyjdzie już tworzyć profesjonalne rozwiązania biznesowe, to i tak nie sięga do książki, lecz prosi o pomoc wujka Google.