Przydatne triki w JavaScript #1

JavaScript w wersji 1.8 to kawałek bardzo fajnych i użytecznych funkcji oraz konstrukcji. Niestety, nim wszystkie przeglądarki zaczną obsługiwać tę wersję języka, minie spory kawałek czasu. Powszechnie używana wersja 1.5 na szczęście także potrafi pokazać pazurki.

(5).plus(1).minus(2) = 4

Jedną z zalet JS jest możliwość używania podobnych, dzięki łatwej implementacji tzw. chainingu, czyli łączenia łańcuchów kodu. Jak coś podobnego zrobić?

Zaczynamy od interpretacji działania takiej linijki kodu. Wszystko zaczyna się od liczby 5 zapisanej w nawiasie. Jest to dla nas sygnał, że liczba 5 musi być obiektem, ponieważ inaczej niemożliwe byłoby wykonywanie nań żadnych funkcji. Jakiego typu obiektem jest ta liczba możemy sprawdzić dzięki typeof.

alert(typeof 5);

Powyższa linijka zwróci nam dumnie brzmiące słowo number. Nie trzeba dużej bystrości by stwierdzić, że do czynienia mamy z obiektem reprezentującym liczbę.

Próba wywołania na tym obiekcie funkcji plus() bądź minus() kończy się jednak niepowodzeniem – JS nie potrafi odnaleźć takich funkcji, które byłyby użyte w kontekście obiektu number. Ergo, nie posiada on takich metod/funkcji. JavaScript jest jednak na tyle elastyczny, że udostępnia stosowne narzędzia, które pomagają nam uzupełnić obiekt o jakie tylko zapragniemy funkcje. Jest to właściwość prototype, używana tak:

obiekt.prototype.nazwa_nowej_funkcji = function() {};

Po tym zabiegu obiekt będzie posiadał w swoim asortymencie funkcję o nazwie nazwa_nowej_funkcji:

obiekt.nazwa_nowej_funkcji();

Pora teraz wykorzystać to w kontekście obiektu number. Zaimplementujmy więc funkcję plus:

number.prototype.plus = function(i) { return parseInt(this+i); };

Przetestujmy teraz co nieco:

var liczba = (5).plus(5); alert(liczba);

Nie działa… Przyczyna jest prosta – istnieje tylko obiekt Number, a nie number. Wszystko przez typeof, które zwraca nazwy typów obiektów pisane małymi literami. Poprawmy więc, co trzeba:

Number.prototype.plus = function(i) { return parseInt(this+i); };

Teraz podany przykład działa w najlepsze.

Czas na dodanie funkcji minus:

Number.prototype.minus = function(i) { return parseInt(this-i); };

I przetestowanie przykładu z nagłówka:

var liczba = (5).plus(1).minus(2); alert(liczba);

Powinna pojawić się liczba 4.

Zastanówmy się teraz spokojnie jak to wszystko działa. Otóż najpierw JS szuka wewnątrz obiektu Number funkcji plus. Gdy się to stanie, funkcja plus wykonuje się, zwracając liczbę będącą równowartością sumy this i liczby podanej w argumencie funkcji (jeden). Jako że 5 to liczba, jak i obiekt, na którym wykonujemy funkcję plus, this odnosi się właśnie do jego wartości (5). Funkcja minus wykonywana jest podobnie – tyle że w this, zamiast pięć, mamy teraz sześć, bo minus() odnosi się do obiektu, w kontekście którego jest wykonywana (to, co jest przed kropką oddzielającą funkcję minus od reszty, gwoli ścisłości).

dodaj(2)(3)

Coś takiego jest dość proste do zrobienia, wystarczy trochę pomyśleć. Przecież aby można było podstawić drugi nawias (3) do dodaj(2), dodaj(2) musi być funkcją. Tylko funkcje można uruchamiać w takim zapisie: nazwafunkcji(argument). dodaj(2) musi być więc funkcją, co znaczy nie więcej, jak to, że musi zwracać w swoim ciele … inną funkcję. Coś w ten deseń:

function funkcja() { return function() {}; }

Funkcja, zwracająca anonimową funkcję. Zastosujmy to teraz do naszego przykładu:

function dodaj() { return function(){}; }

No dobra, ale co dalej? Jak widać, funkcja dodaj musi przyjąć jako argument dowolną liczbę, a potem, podstawić ją do funkcji, którą zwraca, by tam dodać ją do argumentu, który przyjmuje z kolei zwracana, anonimowa funkcja.

function dodaj(i) { return function(i2) { return parseInt(i+i2); }; }

parseInt sprawia, że niezależnie jakie wartości byśmy podstawili, JS postara się zawsze powstałą sumę przekonwertować na obiekt typu Number.

Komentarze

1

Za przeproszeniem – zalatuje kursem Wimmera.
Lepiej byś wytłumaczył zasadę działania closures i dziedziczenia prototypowego, a nie dawał konkretne przykłady jakiegoś, średnio przydatnego, zastosowania.

Poza tym, to nie tricki. Trickiem jest
[a, b] = [b, a]

Maciej Łebkowski
2

@Maciej Łebkowski: eeee tam! czepiasz się Maciej!
Zobaczyłeś pewnie coś co już znasz bo to robiłeś (albo jeszcze gorzej – coś co gdzieś jedynie czytałeś) i teraz się ciskasz jak wsza na kołnierzu. Ja znałem Twój ‘trick’, jak ty to nazywasz, i teraz jestem bogatszy o wszystkie te informacje razem wzięte…
Warto zawsze coś dodać albo powiedzieć i ferrante to robi – popieram go – tyle ….

Mnie za to głębiej interesuje działanie this . Chcę poznać całościowo zasady jakie obowiązują przy określaniu co to zwróci….

3

this jest referencją do obiektu, na którym odpalana jest metoda, bądź do obiektu globalnego (window), jeśli nie ma „zaczepienia” w innym obiekcie (vide, onclick=obiekt.metoda „zrzuca” metodę z obiektu). Wyjątkami od powyższego są metody call oraz apply — jeśli ich użyjesz, this przyjmie wartość pierwszego argumentu.
No i oczywiście w przypadku wywołania ‘new Sample()’ this będzie nowo utworzonym obiektem.

Tłumaczenie trochę lakoniczne, wybacz. Ale gdyby, zamiast konkretnego przykładu, ferrante pokazał działanie closures, wiedziałbyś jak przy pomocy .apply zadbać o to, żeby this zawsze wskazywało na odpowiedni obiekt. Tyle ode mnie.

Maciej Łebkowski
4

Lebkowski: Tez mi przykro, ze ja prowadze tego bloga, a nie Ty. Nawet nie chce mi sie dyskutowac z ta pieniacka retoryka, kiedy oczekujesz, ze przy kazdym teoretycznym przykladzie podam serie gotowych rozwiazan na nim opartych. Kazdy wykorzysta ten artykul na swoj sposob. A zapewniam, ze uzywajac podanych wyzej technik mozna stworzyc wiele ciekawych rzeczy. Temu Panu tez powiedz, ze wymaga nie wiadomo co: http://dmitry.baranovskiy.com/topic/javascript-tasks

Pozdrawiam

5

Nie przejmuj się zawistnymi komentarzami. Dla mnie ten wpis to częściowo nowinka, a zarazem ciekawostka. Nie zrażaj się i pisz więcej :)

6

A ja mam pytanie jak stworzyć podobnie działający kod do np jquery. Chodzi mi tutaj o funckję $() i możliwość wywoływania innych metod lub pobierania/zmieniania wartości własności obiektów zwróconych przez tą funkcję.
Nie wiem czy dobrze rozumiem: ta funkcja zwraca obiekt jQuery, w którym przechowywany jest znaleziony obiekt drzewa DOM. Pytanie tylko: w jaki sposób to wszystko następnie ze sobą współpracuje?
Trochę zamieszałem chyba, ale mam nadzieję, że zrozumiecie o co mi chodzi ;)

Drugie pytanie: pod FF i Operą działa Object.prototype.funkcja = function…, aby dodać jakąś metodę do każdego obiektu, pod IE taki kod jednak nie działa. Czy można w jakiś sposób zmusić IE do poprawnego działania?

fou
7

fou – w nastepnym wpisie bedzie o tym ;-)

Co do prototype:

var a = function(){};
a.prototype.msg = function() { alert('msg'); };
b = new a();
b.msg();

Dziala…

8

ferrante, to proszę o szybkie napisanie następnego wpisu ;)

to co podałeś oczywiście będzie działać, ale zrobić coś takiego :
var div = $('#dupa');
vat tab_a = div.$('a');
tab_a[0].hide();
// itd...

Nie wiem jak dodawać własne metody do obiektów drzewa DOM. Sprawa ze stringami wygląda prościej – można to załatwić wspomnianym prototype.

fou
9

Sek w tym, że wcale nie podajesz teorii, tylko jakiś konkretny przykład — w moim odczuciu zupełnie nieprzydatny podczas nauki. Kazać coś takiego zrobić jako test sprawdzenia wiedzy – owszem, ma sens. eot

Maciej Łebkowski
10

Fajna byłaby sztuczka, która pozwalała by dodać do siebie dowolną liczbę liczb przez () ale to tylko sztuczka, bo zagnieżdżanie w sobie funkcji jest beznadziejnie niewydajne. Nie znajduję dla tego zastosowania kiedy można użyć „atributes” w funkcjach. Zwracanie funkcji z funkcji jest też podatne na wyciekanie pamięci. Ogólnie odradza się robienia interfejsu fabryki w JS.

Adrian Kalbarczyk
11

Bardzo pomocny tekst…

Czytelnik
12

Dziękuję za świetny artykuł. Nie zrażaj się pieniactwem pseudo-expertów:)

Marcin
13

Proszę pomóżcie,jestem totalną nogą jeżeli chodzi o tego typu sprawy
mam napisać funkcje która ma nosić nazwę difference.
Dwie dowolne liczby odjąć od siebie tak by wynik wrócił jako liczba dodatnia.
Coś w stylu a-b gdzie a>b i to ma być w JavaScript.
Jeśli to nie problem dla Was drodzy czytelnicy napiszcie jak ma taki kod funkcji wyglądać.
Z góry dziękuje.

Adrian
14

var difference = function() {
var a = 5;
var b = 3;
var wynik = a-b;
alert(wynik);
}

Dodaj komentarz

Dozwolone tagi: <blockquote>, <code>, <strong>