!function f(){ console.log(['!', f, '();'].join('')); }();
niedziela, 18 grudnia 2011
Quine w JavaScript
Quine - czyli program wypisujący własny kod źródłowy.
niedziela, 21 sierpnia 2011
Własny EventListener
Kolejny raz w mojej pracy zaszła potrzeba zaimplementowania EventListener`a. Kiedy widzę, że muszę napisać drugi raz ten sam kod zawsze refaktoryzuję ten istniejący tak aby wspólną jego część przenieść do jednego niezależnego miejsca w kodzie.
Ewentualne zmiany kodu w więcej niż jednym miejscu nie wróżą niczego dobrego. Pozwólmy aby nasza dalsza praca nad kodem była miła i przyjemna. JavaScript doskonale nam w tym pomoże.
Klasa EventListener pozwala nam na utworzenie własnego nasłuchiwacza zdarzeń. Dzięki niej w wystarczy, że w jednym miejscu aplikacji stworzymy kod odpowiedzialny za wykrycie jakiegoś zdarzenia, a w wielu innych dowolnych miejscach tylko podłączamy się do niego. Innych zalet powyższego wzorca projektowego chyba nie muszę tłumaczyć.
Obiekt zwracany przez metodę add posiada metodę detach, która pozwala odpiąć dodane zdarzenie. Ta z kolei zwraca obiekt zawietający metodę attach, która pozwala na powrotne podpięcie zdarzenia itd.
Podobnie jest z metodą remove naszej klasy. Z tą różnicą, że ta w pierwszej kolejności zwraca obiekt z metodą attach.
Metoda invoke oczywiście służy do wywołania funkcji podpiętych pod zadane zdarzenie. Pierwszym argumentem tej metody jest nazwa wywołanego zdarzenia. Kolejne argumenty są dowolne i opcjonalne. Będą one przekazane do każdej z podpiętych do zdarzenia funkcji.
Ewentualne zmiany kodu w więcej niż jednym miejscu nie wróżą niczego dobrego. Pozwólmy aby nasza dalsza praca nad kodem była miła i przyjemna. JavaScript doskonale nam w tym pomoże.
var EventListener = function(enames){ var events = {}; for( var i = 0; i < enames.length; ++i ){ events[enames[i]] = []; } return { add: function(ename, fn){ var that = this; var i = events[ename].push(fn) - 1; return { detach: function(){ delete events[ename][i]; return { attach: function(){ return that.add(ename, fn); } }; } }; }, remove: function(ename, fn){ var that = this; delete events[ename][ events[ename].indexOf(fn) ]; return { attach: function(){ return that.add(ename, fn); } }; }, invoke: function(ename){ var arg = [].slice.call(arguments, 1); var res = []; for( var i in events[ename] ){ if( events[ename].hasOwnProperty(i) ){ res.push(events[ename][i].apply(this, arg)); } } return res; }, toString: function(){ return '[EventListener: '+ enames.join(', ') +']'; } }; };
Klasa EventListener pozwala nam na utworzenie własnego nasłuchiwacza zdarzeń. Dzięki niej w wystarczy, że w jednym miejscu aplikacji stworzymy kod odpowiedzialny za wykrycie jakiegoś zdarzenia, a w wielu innych dowolnych miejscach tylko podłączamy się do niego. Innych zalet powyższego wzorca projektowego chyba nie muszę tłumaczyć.
Obiekt zwracany przez metodę add posiada metodę detach, która pozwala odpiąć dodane zdarzenie. Ta z kolei zwraca obiekt zawietający metodę attach, która pozwala na powrotne podpięcie zdarzenia itd.
Podobnie jest z metodą remove naszej klasy. Z tą różnicą, że ta w pierwszej kolejności zwraca obiekt z metodą attach.
Metoda invoke oczywiście służy do wywołania funkcji podpiętych pod zadane zdarzenie. Pierwszym argumentem tej metody jest nazwa wywołanego zdarzenia. Kolejne argumenty są dowolne i opcjonalne. Będą one przekazane do każdej z podpiętych do zdarzenia funkcji.
czwartek, 18 sierpnia 2011
Object implicit
Object.prototype.implicit = function(obj){ for( var i in obj ){ if( obj.hasOwnProperty(i) && typeof this[i] === 'undefined' ){ this[i] = obj[i]; } } return this; };
Całkiem przydatna funkcja umożliwiająca łączenie dwóch obiektów. Działa podobnie do już opisanej przeze mnie funkcji update. Różnica polega na tym, że metoda implicit nie nadpisuje już istniejących właściwości obiektu.
Animation, czyli jak sprawnie animować elementy strony
Poprzednio pisałem na temat animacji. Tym razem rozbudowałem tę funkcję o dodatkowe przydatne możiwości.
Przede wszystkim funkcja (klasa) nie zwraca już identyfikatora zwracanego przez funkcję setInterval. Zamiast tego zwraca obiekt z dwiema (nie licząc metody toString nadpisującej tę domyślną dla obiektu) metodami.
Pierwsza metoda stop umożliwia zatrzymanie animacji. Wywołanie jej z pierwszym parametrem równym true spowoduje wywołanie również funkcji, wywoływanej w chwili normalnego zakończenia animacji. Przez normalne zakończenie rozumiemy odtworzenie animacji do końca.
Kolejna metoda zwracanego obiektu pozwala pobrać aktualny stan animacji z zakresu <0; 1>.
Dodatkowo do opisanych już wcześniej parametrów dodałem dwa następujące.
Transition - pozwala przekazać funkcję przejścia. Dzięki temu możemy w prosty sposób zrealizować np. łagodne rozpoczęcie i/lub zakończenie animacji.
Kolejny nowy parametr at jest nie mniej ciekawy. Pozwala bowiem on na rozpoczęcie animacji od zadanego etapu. Animacja z niezerową wartością dla tego parametru będzie trwała już krócej czyli nie będzie rozciągnięta na zadaną ilość klatek (frames) a zostanie ucięta, co pozwala na niesamowicie proste zaimplementowanie animacji, która może zostać w dowolnej chwili przerwana i odtworzona wstecz do punktu wyjściowego.
Na koniec dodam, że "klasa" korzysta z funkcji implicit, w celu ustawienia domyślnych parametrów wywołania funkcji.
var Animation = function(fn, params){ params = params || {}; params = params.implicit({ frames: 10, fn: function(){}, t: 30, transition: null, at: 0 }); var frame = Math.round(params.at * params.frames); var progress; var run = (function(){ return params.transition ? function(p){ progress = p; fn.call(res, params.transition(progress), params); } : function(p){ progress = p; fn.call(res, progress, params); }; })(); var iid = setInterval(function(){ run( (++frame)/params.frames ); if( frame >= params.frames ){ clearInterval(iid); params.fn.call(res, params); } }, params.t); run( frame/params.frames ); var res = { stop: function(execEndFn){ execEndFn = execEndFn || false; clearInterval(iid); if( execEndFn ){ params.fn.call(res, params); } return this.progress(); }, progress: function(){ return progress; }, toString: function(){ return '[Animation: '+ Math.round(progress * 1e2) +'%]'; } }; return res; };
Przede wszystkim funkcja (klasa) nie zwraca już identyfikatora zwracanego przez funkcję setInterval. Zamiast tego zwraca obiekt z dwiema (nie licząc metody toString nadpisującej tę domyślną dla obiektu) metodami.
Pierwsza metoda stop umożliwia zatrzymanie animacji. Wywołanie jej z pierwszym parametrem równym true spowoduje wywołanie również funkcji, wywoływanej w chwili normalnego zakończenia animacji. Przez normalne zakończenie rozumiemy odtworzenie animacji do końca.
Kolejna metoda zwracanego obiektu pozwala pobrać aktualny stan animacji z zakresu <0; 1>.
Dodatkowo do opisanych już wcześniej parametrów dodałem dwa następujące.
Transition - pozwala przekazać funkcję przejścia. Dzięki temu możemy w prosty sposób zrealizować np. łagodne rozpoczęcie i/lub zakończenie animacji.
Kolejny nowy parametr at jest nie mniej ciekawy. Pozwala bowiem on na rozpoczęcie animacji od zadanego etapu. Animacja z niezerową wartością dla tego parametru będzie trwała już krócej czyli nie będzie rozciągnięta na zadaną ilość klatek (frames) a zostanie ucięta, co pozwala na niesamowicie proste zaimplementowanie animacji, która może zostać w dowolnej chwili przerwana i odtworzona wstecz do punktu wyjściowego.
Na koniec dodam, że "klasa" korzysta z funkcji implicit, w celu ustawienia domyślnych parametrów wywołania funkcji.
niedziela, 14 sierpnia 2011
Object update
Object.prototype.update = function(obj){ for( var i in obj ){ if( obj.hasOwnProperty(i) ){ this[i] = obj[i]; } } return this; };
Funkcja update pozwala w prosty sposób rozbudować lub nadpisać dowolne właściwości obiektu.
Ponieważ funkcja operuje na referencji do obiektu, oraz zwraca tę referencję możemy z niej skorzystać na dwa sposoby.
Pierwszy z nich polega na utworzeniu obiektu i nadpisaniu/rozszerzeniu jego metod/wartości.
var obj = { a: 1, b: 2 }; obj.update({ b: 3, c: 4 }); console.log(obj);
Drugi sposób to wywołanie metody update na naszym obiekcie w chwili jego utworzenia i zwrócenie na jego miejsce nowego obiektu.
var obj = { a: 1, b: 2 }.update({ b: 3, c: 4 }); console.log(obj);
startInterval czyli natychmiastowy setInterval
var startInterval = function(fn, delay){ var arg = [].slice.call(arguments, 2), that = this; var newFn = function(){ fn.apply(that, arg); }; newFn(); return setInterval(newFn, delay); };
Powyżej zamieszczam funkcję startInterval. Działa dokładnie tak jak setInterval (z wyjątkiem możliwości przekazania pierwszego parametru jako String) z tą różnicą, że pierwsze wywołanie funkcji następuje natychmiastowo.
Funkcja jest wygodną alternatywą dla setInterval. Dzięki niej nie trzeba w kodzie tworzyć dodatkowych nazw dla funkcji, aby wywołać ją, oraz przekazać jako parametr do setInterval.
niedziela, 12 czerwca 2011
Domyślne parametry funkcji
Funkcja i domyślne jej parametry w JavaScript? JavaScript nigdy nie oferował mechanizmu nadawania domyślnych wartości niezdefiniowanym parametrom przy jej wywołaniu.
Można sobie z tą niedogodnością poradzić w całkiem prosty sposób i wiele osób tak to właśnie robi. Poniżej zamieszczam kawałek kodu.
Działa całkiem nieźle. Co jednak jeśli wywołamy funkcję w poniższy sposób?
Funkcja wyświetla 25. Przecież wysłaliśmy 0 (zero). Dzieje się tak, ponieważ poniższa konstrukcja traktuje liczbę 0 jako wartość false, dlatego do zmiennej a przypisywana jest alternatywna wartość 25.
Skoro znamy już problemy wynikające z powyższych konstrukcji przejdźmy do rozwiązania problemu w inny, bardziej elegancki sposób.
Poniżej zamieszczam funkcję która pozwala ze zwykłej funkcji stworzyć funkcję przyjmującą domyślne parametry. Kod zawiera również funkcję testową oraz kilka jej wywołań.
Stworzenie funkcji z domyślnymi parametrami jest teraz dziecinnie proste. Wystarczy zadeklarować dowolną funkcję a zaraz za nią wywołać naszą metodę z argumentami, które mają być domyślne.
Warto pamiętać o tym, że powyższe udogodnienie będzie spowalniać działanie skryptu. Każde wywołanie zwróconej funkcji powoduje wywołanie kilku dodatkowych instrukcji. Nadużywanie tego udogodnienia może okazać się nierozsądne, w szczególności w przypadku prostych, wielokrotnie powtarzanych funkcji.
Można sobie z tą niedogodnością poradzić w całkiem prosty sposób i wiele osób tak to właśnie robi. Poniżej zamieszczam kawałek kodu.
var fn = function(a, b, c){ a = a || 25; b = b || [2, 5]; c = c || 'domyslny'; alert( 'liczba: ' + a + '\n' + 'tablica: ' + b + '\n' + 'string: ' + c ); }; fn(5, [1,2,3], 'test'); fn(5, [1,2,3]); fn(5); fn();
Działa całkiem nieźle. Co jednak jeśli wywołamy funkcję w poniższy sposób?
fn(0);
Funkcja wyświetla 25. Przecież wysłaliśmy 0 (zero). Dzieje się tak, ponieważ poniższa konstrukcja traktuje liczbę 0 jako wartość false, dlatego do zmiennej a przypisywana jest alternatywna wartość 25.
a = a || 25;
Skoro znamy już problemy wynikające z powyższych konstrukcji przejdźmy do rozwiązania problemu w inny, bardziej elegancki sposób.
Poniżej zamieszczam funkcję która pozwala ze zwykłej funkcji stworzyć funkcję przyjmującą domyślne parametry. Kod zawiera również funkcję testową oraz kilka jej wywołań.
Function.prototype.params = function(){ var that = this, arg = [].splice.apply(arguments, [0, arguments.length]); return function(){ return that.apply(this, [].splice.apply(arguments, [0]).concat( arg.slice(arguments.length) ) ); }; }; var test = function(a, b){ alert( a + '\n' + b ); }.params(11, 22); test(1, 2); test(1); test();
Stworzenie funkcji z domyślnymi parametrami jest teraz dziecinnie proste. Wystarczy zadeklarować dowolną funkcję a zaraz za nią wywołać naszą metodę z argumentami, które mają być domyślne.
Warto pamiętać o tym, że powyższe udogodnienie będzie spowalniać działanie skryptu. Każde wywołanie zwróconej funkcji powoduje wywołanie kilku dodatkowych instrukcji. Nadużywanie tego udogodnienia może okazać się nierozsądne, w szczególności w przypadku prostych, wielokrotnie powtarzanych funkcji.
poniedziałek, 6 czerwca 2011
Animacja jednostkowa
Animacja jednostkowa to funkcja którą napisałem, po to aby ułatwić sobie tworzenie animacji w swoich aplikacjach.
Nazywam ją tak, ponieważ umożliwia mi proste zbudowanie animacji dowolnej wielkości (właściwości obiektu) w prosty i elegancki sposób poprzez wywołanie mojej funkcji z parametrem - liczbą z zakresu od 0 do 1 włącznie, która określa aktualny stan animacji.
Poniżej zamieszczam kod tytułowej funkcji a zaraz za nim krótki opis sposobu używania.
Jak widać funkcja przyjmuje dwa parametry: wskaźnik do funkcji oraz obiekt opcjonalnych parametrów.
fn - to wskaźnik do głównej funkcji wykonawczej, która będzie decydowała o tym co będziemy animować.
Zmienna params przyjmuje 3 opcjonalne parametry:
- frames - ilość klatek animacji,
- t - interwał pomiędzy kolejnymi klatkami animacji,
- fn - funkcja, która zostanie wywołana zaraz po zakończeniu działania animacji.
Funkcja zwraca wartość Number zwracaną przez wbudowaną funkcję setInterval. Pozwala to na wcześniejsze zakończenie działania animacji.
Jak widać funkcja jest niczym więcej jak wzbogaceniem funkcji setInterval o dodatkowe funkcjonalności.
Mimo wszystko potrafi bardzo uprościć pisany kod.
Do jej głównych zalet można zaliczyć:
- zwracany bieżący stan animacji mieszczący się zawsze w przedziale domkniętym <0; 1>,
- pierwsze wywołanie funkcji fn(0) jest wykonywane synchronicznie, w przeciwieństwie do zastosowania samej funkcji setInterval, gdzie na pierwsze wywołanie zadanej funkcji skrypt musi odczekać zadany czas. Dzięki temu unikniemy krótkotrwałych stanów nieustalonych przebiegu animacji,
- i wreszcie możliwość dodania wskaźnika do funkcji wywoływanej na zakończenie animacji - prosty sposób na wykrycie końca animacji bez żadnych dodatkowych instrukcji warunkowych.
Nazywam ją tak, ponieważ umożliwia mi proste zbudowanie animacji dowolnej wielkości (właściwości obiektu) w prosty i elegancki sposób poprzez wywołanie mojej funkcji z parametrem - liczbą z zakresu od 0 do 1 włącznie, która określa aktualny stan animacji.
Poniżej zamieszczam kod tytułowej funkcji a zaraz za nim krótki opis sposobu używania.
var animation = function(fn, params){ params = params || {}; params = { frames: params.frames || 10, fn: params.fn || null, t: params.t || 30 }; var frame = 0; fn(0); var iid = setInterval(function(){ fn( (++frame)/params.frames ); if( frame >= (params.frames) ){ clearInterval(iid); params.fn(); } }, params.t); return iid; };
Jak widać funkcja przyjmuje dwa parametry: wskaźnik do funkcji oraz obiekt opcjonalnych parametrów.
fn - to wskaźnik do głównej funkcji wykonawczej, która będzie decydowała o tym co będziemy animować.
Zmienna params przyjmuje 3 opcjonalne parametry:
- frames - ilość klatek animacji,
- t - interwał pomiędzy kolejnymi klatkami animacji,
- fn - funkcja, która zostanie wywołana zaraz po zakończeniu działania animacji.
Funkcja zwraca wartość Number zwracaną przez wbudowaną funkcję setInterval. Pozwala to na wcześniejsze zakończenie działania animacji.
// prosty kod pokazujacy sposob uzycia funkcji animation(function(x){ alert(x); }, { frames: 10, fn: function(){ alert('koniec'); } });
Jak widać funkcja jest niczym więcej jak wzbogaceniem funkcji setInterval o dodatkowe funkcjonalności.
Mimo wszystko potrafi bardzo uprościć pisany kod.
Do jej głównych zalet można zaliczyć:
- zwracany bieżący stan animacji mieszczący się zawsze w przedziale domkniętym <0; 1>,
- pierwsze wywołanie funkcji fn(0) jest wykonywane synchronicznie, w przeciwieństwie do zastosowania samej funkcji setInterval, gdzie na pierwsze wywołanie zadanej funkcji skrypt musi odczekać zadany czas. Dzięki temu unikniemy krótkotrwałych stanów nieustalonych przebiegu animacji,
- i wreszcie możliwość dodania wskaźnika do funkcji wywoływanej na zakończenie animacji - prosty sposób na wykrycie końca animacji bez żadnych dodatkowych instrukcji warunkowych.
środa, 13 kwietnia 2011
Inline scripts
Proste skrypty JavaScript, wklejane do paska adresu które mogą ułatwić naszą codzienną pracę.
Liczenie średniej z zestawu danych.
Zaznaczenie wszystkich pól checkbox na stronie. Naturalnie odznaczenie można zrealizować przez zamianę true na false.
Liczenie średniej z zestawu danych.
javascript:d={s:0,l:0};while(d.i=parseInt(prompt('podaj liczbe do sredniej (zero konczy ciag danych)',''),10)){d.s+=d.i;++d.l;}alert('srednia z '+d.l+' danych wynosi: '+(d.s/d.l));
Zaznaczenie wszystkich pól checkbox na stronie. Naturalnie odznaczenie można zrealizować przez zamianę true na false.
javascript:p=document.getElementsByTagName('input');for(i=0;i<p.length;++i){if(p[i].type==='checkbox'){p[i].checked=true;}};alert('ok');
sobota, 2 kwietnia 2011
Problem z funkcjami setTimeout oraz setInterval w IE
W tym poście opiszę jak rozwiązać problem z przekazywaniem argumentów w funkcji setTimeout w przeglądarce Internet Explorer, który pojawił się w trakcie mojej pracy nad kodem JavaScript.
Problem polega na tym, że przeglądarka IE nie przekazuje argumentów do funkcji, gdy funkcja jest wywoływana z opóźnieniem (setTimeout) lub w interwałach czasowych (setInterval).
Problem polega na tym, że przeglądarka IE nie przekazuje argumentów do funkcji, gdy funkcja jest wywoływana z opóźnieniem (setTimeout) lub w interwałach czasowych (setInterval).
// przydatne funkcje Array.prototype.clone = function(){ return this.slice(0); }; Object.prototype.toArray = function(){ var arr = []; for( var i in this ){ if( this.hasOwnProperty(i) ){ arr.push(this[i]); } } return arr; }; var argToArray = function(arg){ var arr = []; for( var i = 0; i < arg.length; ++i ){ if( arg.hasOwnProperty(i) ){ arr.push(arg[i]); } } return arr; }; // safeTimeout - odpowiednik funkcji setTimeout wykorzystujacy ta funkcje // oraz dzialajacy prawidlowo rowniez w IE var safeTimeout = function(fn, t){ if( typeof(fn) == typeof(function(){}) ){ var arg = argToArray(arguments).slice(2); return setTimeout(function(){ fn.apply(this, arg); }, t); } return setTimeout(fn, t); }; // safeInterval - odpowiednik funkcji setInterval var safeInterval = function(fn, t){ if( typeof(fn) == typeof(function(){}) ){ var arg = argToArray(arguments).slice(2); return setInterval(function(){ fn.apply(this, arg); }, t); } return setInterval(fn, t); };
// funkcja testowa sprawdzajaca poprawnosc powyzszych funkcji var testFunction = function(a, b){ alert('1. ' + a + '\n2. ' + b); }; // uruchomienie setTimeout w IE nie da oczekiwanego rezultatu (screen powyzej) // parametry nie sa przekazywane do wywolywanej funkcji setTimeout(testFunction, 1, 'pierwszy argument', 'drugi argument'); // wywolanie funkcji naprawiajacej ten problem przynosi oczekiwany rezultat safeTimeout(testFunction, 1, 'pierwszy argument', 'drugi argument'); // test funkcji dla pierwszego parametru bedacego String`iem safeTimeout('alert(\'evaluation test\')', 1); // uruchomienie funkcji czyszczacej interwal czasowy z opoznieniem 5 sekund safeTimeout(function(iid){ clearInterval(iid); }, 5e3, // uruchomienie funcki testowej w interwalach co 1 sekunde safeInterval(testFunction, 1e3, 'pierwszy argument', 'drugi argument') );
Subskrybuj:
Posty (Atom)