WTFJS - rozjaśnienie umysłu

6 komentarzy

Blog WTFJS o dziwactwach w JavaScriptcie widział już pewnie każdy. Niektóre przykłady są naprawdę fajne :) Co więcej — niektóre działają mi w sposób przedstawiony przez autorów tylko czasami, choć to pewnie nie problem JavaScriptu, a Firebuga :).

Jako, że lubię wiedzieć dlaczego tak to działa, a nie inaczej, staram się wyjaśnić sobie o co chodzi w każdym przykładzie. Niektórych nadal nie rozumiem, część jest oczywista, część ma rozwiązania podane na stronie, a te ciekawsze postaram się wyjaśnić w tym (i może następnych) wpisach.

Jestem prawie obiektowy

typeof "abc" == "string" // true
typeof String("abc") == "string" // true
String("abc") == "abc" // true -- same types get casted to equal each other
 
String("abc") instanceof String // false -- hmmm...
(new String("abc")) instanceof String // true
String("abc") == (new String("abc")) // true -- wait, wtf?

new String() to nie String(), a "string" to nie "object". Proste? :) Trochę wolniej:

Pierwsza linia jest oczywista, ale warta zapamiętania — typem literału stringa jest "string". W drugiej mogłoby się wydawać że tworzymy obiekt, ale nie — konstruktor String wywołany bez new po prostu rzutuje na stringa. Trzecia linia tylko to potwierdza.

Czwarta linia to nawiązanie do tytułu jaki nadałem tej sekcji — Jestem prawie obiektowy. Autor tego przykładu zdziwił się skąd takie zachowanie, ponieważ pewnie nie wiedział o typach prostych w JavaScriptcie. Otóż wartości: 1, "a", true nie są typu "object" tylko odpowiednio "number", "string", "boolean". Nie są z tego powodu instancjami swoich podobnie brzmiących z nazwy klas (ale uwaga — czerpią z ich prototypów). Jest to zachowanie całkiem uzasadnione (choć niefajne w działaniu), ponieważ w innym przypadku (gdyby typy proste nie istniały) nie działało by porównanie ===.

Wyobraźmy sobie sytuację, w której pisząc "a" tworzymy nowy obiekt typu "string". Porównanie dwóch zmiennych, które są obiektami za pomocą operatora === sprawdza, czy zmienne te odnoszą się do dokładnie tego samego obiektu w pamięci. Tak więc w naszej hipotetycznej sytuacji otrzymalibyśmy:

"a" === "a"; // -> false

Uups. Po lewej obiekt, po prawej obiekt, ale to nie te same obiekty. Na szczęście teraz jest tak:

"a" === "a"; // -> true
new String("a") === new String("a"); // -> false
true === true; // -> true
new Boolean(true) === new Boolean(true); // -> false

Nie twierdzę, że jestem fanem takiego rozwiązania. Nie jestem pewien, ale chyba w Javie jest podobnie, za to na przykład już w Rubym jest to rozwiązane lepiej (ktoś potwierdzi?). Cóż — nic nie jest idealne ;).

Wróćmy jednak do przykładu. W piątej linii wreszcie tworzymy obiekt typu String. Dla przypomnienia:

typeof new String("a"); // -> "object"
typeof String("a"); // -> "string"

Ostatnia linia powinna być już oczywista jeśli powiem, że operator == przeprowadza konwersję zmiennych stojących po obu jego stronach do jednego typu (którego, tego nie wiem — pewnie zmiennej po lewej — kto ma ochotę niech sprawdzi w specyfikacji i napisze w komentarzu :). Tak więc z porównania String("abc") == (new String("abc")) otrzymujemy porównanie "a" == "a". Dla porównania:

String("abc") === (new String("abc")); // -> false

Widać więc, że trzeba uważać na krótki operator porównania i stosować go z głową. Polecam przyzwyczajenie się do używania potrójnego operatora i tylko w szczególnych wypadkach, kiedy na przykład znamy typy zmiennych (typeof x == "string"), używanie podwójnego.

Jestem prawie obiektowy, ale umiem porównywać

Kolejny przykład będzie tylko rozwinięciem poprzedniej części. Oto on:

(1) === 1; // true
Number.prototype.isOne = function () { return this === 1; }
(1).isOne(); // false!
Number.prototype.reallyIsOne = function () { return this - 1 === 0; }
(1).reallyIsOne(); // true

Pierwsza linia powinna być oczywista — nawias nie ma znaczenia. Druga i trzecia to ścisłe nawiązanie do poprzedniej części. W isOne typeof this; // -> "object". Mimo, że wywołujemy metodę na zmiennej typu "number", to jest ona po cichu rzutowana na "object" — dziwne, ale do zrozumienia — ciężko żeby this nie było obiektem.

Dwie ostatnie linie, to dynamiczne rzutowanie. Operacja odejmowania stara się rzutować obie zmienne na typ liczbowy. this jest rzutowane na 1 i reszta jest jasna. Równie dobrze można byłoby zapisać reallyIsOne w ten sposób:

Number.prototype.reallyIsOne = function () { return +this === 1; };

Używaj średników!

Poprzedni przykład przekleiłem na czysto z WTFJS. Jeśli będziecie go w tej postaci wklejać do Firebuga, to możecie się spotkać z latającymi wyjątkami. Skąd ten problem? Na końcu drugiej i czwartej linii brakuje średników i przynajmniej firefoksowy silnik wysiada. Rada na przyszłość — średniki dobre są.

Niezrozumiałe dla mnie

Tego już nie wytłumaczę :). Choć — obstawiałbym, że problem leży gdzieś w standardzie reprezentacji liczb zmiennoprzecinkowych, ale to tylko mój domysł.

Number.MAX_VALUE*1.0000000000000001 === (1/0) // false
Number.MAX_VALUE*1.0000000000000002 === (1/0) // true

GąsienicaOpublikowane na licencji CC przez Trufflepig.

W poprzednim artykule o obiektach w JavaScriptcie poruszyłem kwestię ich tworzenia, dostępu do właściwości, kontekstu wywoływania metod oraz wspomniałem o kilku cechach. Czyste literały obiektów same w sobie są już w tym języku bardzo przydatne — przekonał się pewnie o tym każdy kto korzystał z jakiejś biblioteki typu MooTools czy Prototype. Pozwalają na przykład w dość wygodny sposób na podanie dowolnej liczby nazwanych argumentów funkcji:

new Ajax.Request('/your/url', {
	parameters: { id: 12, action: 'sth' },
	onSuccess: function (transport) {
		alert(transport.responseText);
	},
	evalJS: false
});

Pisałem ostatnio o tym, że w JavaScriptcie nie ma klas. Skąd więc w przykładzie słówko kluczowe new, które w typowym języku służy do tworzenia instancji obiektu na bazie jakiejś klasy? Otóż okazuje się, że w JavaScriptcie wprowadzono twór zwany konstruktorami obiektów.

Konstruktor obiektu

Zwyczajnie zaczyna się od definiowania klasy jakiegoś kotka, samochodu, czy figury geometrycznej. Nudy. Chciałem wymyślić coś bardziej konstruktywnego i spośród wszystkich dostępnych na moim biurku obiektów wybrałem butelkę po miodzie pitnym (pustą niestety). Myślę, że konstruktory dla napoju alkoholowego i butelki będą wystarczająco oryginalne :). Do dzieła:

var Alkohol = function (nazwa, ilosc_procentow, kolor) {
	this.nazwa = nazwa;
	this.ilosc_procentow = ilosc_procentow;
	this.kolor = kolor;
};
var ButelkaAlkoholu = function (alkohol, pojemnosc, rok_produkcji, kraj_produkcji) {
	this.alkohol = alkohol;    
	this.pojemnosc = pojemnosc;
	this.rok_produkcji = rok_produkcji;
	this.kraj_produkcji = kraj_produkcji;
};

Oj tak. JavaScript to dziwoląg. Miały być konstruktory obiektów, a tu znowu pojawiły się funkcje. Z drugiej strony pojawiło się też słowo kluczowe this, więc cały przykład przypomina dwa wycięte z definicji klas napisanych w normalnym języku konstruktory. Na szczęście obiekty tworzymy już normalnie:

var miod = new Alkohol('Miód pitny', 13, '#850');
var butelka_miodu = new ButelkaAlkoholu(miod, 750, 2009, 'Polska');
 
console.dir(miod); // -> screenshot poniżej
console.dir(butelka_miodu); // -> screenshot poniżej
 
butelka_miodu.alkohol.nazwa; // -> "Miód pitny"

Obiekty miód i butelka miodu

Utworzyliśmy w ten sposób dwa obiekty, których struktura została wylistowana przy pomocy Firebuga i którą przedstawiłem na screenie.

W poprzedniej części artykułu pisałem o tym, że obiekt może mieć swoje metody (inaczej funkcje przypisane jako właściwości obiektu). Musi więc być sposób aby w konstruktorze obiektu zdefiniować takie metody. Dodajmy więc kilka linii do konstruktora ButelkaAlkohol:

var ButelkaAlkoholu = function (alkohol, pojemnosc, rok_produkcji, kraj_produkcji) {
	//...
	this.pelna = true;
	this.oproznij = function () {
		this.pelna = false;
	};
};
 
var butelka_miodu = new ButelkaAlkoholu(miod, 750, 2009, 'Polska');
butelka_miodu.pelna; // -> true
butelka_miodu.oproznij();
butelka_miodu.pelna; // -> false

Jak widać, aby utworzyć coś co możemy nazwać metodą, musimy przypisać funkcję do właściwości przyszłego obiektu. Uczulam znowu na działanie słowa this, o czym pisałem już poprzednim razem.

Alternatywna metoda tworzenia konstruktorów obiektów

Naszym celem jest utworzenie obiektu o zadanych przez nas wartościach właściwości. Pamiętając, że najprostszym sposobem na utworzenie obiektu jest skorzystanie z literału możemy dojść do następującej konstrukcji: stwórzmy funkcję zwracającją obiekt zapisany za pomocą literału:

var Alkohol2 = function (nazwa, ilosc_procentow, kolor) {
	var obj = {
		nazwa: nazwa,
		ilosc_procentow: ilosc_procentow,
		kolor: kolor,
		pelna: true,
		oproznij: function () { this.pelna = false; }
	};
 
	return obj;
};
 
var piwo = Alkohol2('piwo Tyskie', 5.6, '#FE3');
piwo; // -> Object nazwa=piwo Tyskie ilosc_procentow=5.6 kolor=#FE3

Tak więc skorzystaliśmy z funkcji w jej czysto funkcyjnym wymiarze. To jednak nie koniec. Dawno już nie było mowy o żadnym JavaScriptowym dziwactwie. Pora na następne. Otóż ten zapis również zadziała:

var piwo2 = new Alkohol2('piwo Tyskie', 5.6, '#FE3');
piwo2; // -> Object nazwa=piwo Tyskie ilosc_procentow=5.6 kolor=#FE3

Zonk.

Anegdota o użyciu new z funkcją

Kiedy dowiedziałem się o powyższym od razu zacząłem się zastanawiać jak w zasadzie JavaScript traktuje new w kontekście funkcji. Pierwsze co ciśnie się na palce, to modyfikacja przykładu z konstruktorem Alkohol2 tak by zwracał "nieobiekt":

var Cons = function () {
	return 5;
};
var obj = new Cons();
obj; // -> Object

Nic ciekawego — jakiś pusty obiekt otrzymaliśmy. Dodajmy więc właściwość (pierwszy sposób tworzenia konstruktorów):

var Cons = function () {
	this.a = 'a';
	return 5;
};
var obj = new Cons();
obj; // -> Object a=a

Ciekawe, choć do wytłumaczenia — return 5 jest pomijane. Połączmy teraz dwa sposoby tworzenia konstruktorów:

var Cons = function () {
	this.a = 'a';
	return { b: 'b' };
};
var obj = new Cons();
obj; // -> Object b=b

Uups. JavaScript pomija właściwości zadeklarowane przy użyciu słowa this. W ten sposób dochodzimy do algorytmu, którym kieruje się ten język (a przynajmniej Firefox — może komuś się chce zajrzeć do specyfikacji? :):

  1. Jeśli zwracana przez funkcję wartość jest obiektem, to wyrażenie new Cons() zwraca ten obiekt.
  2. W przeciwnym wypadku wyrażenie to zwraca obiekt z właściwościami ustalonymi przy pomocy słowa this.

Porównując obydwa sposoby pisania konstruktorów można dojść do wniosku, że lepsza jest ta druga, (ze zwracaniem gotowego obiektu), ponieważ działa na obydwa sposoby: new Cons() i Cons(). Osobiście uważam jednak, że ta metoda jest gorsza — dlaczego? O tym później w tej i w następnej części artykułu.

Elementy programowania obiektowego

Umiemy już tworzyć konstruktory obiektów, które można porównać do klas w zwyczajnym języku. Obiekty tworzone za ich pomocą posiadały do tej pory jednak tylko publiczne właściwości i metody. JavaScript nie udostępnia mechanizmu modyfikatorów dostępu, można jednak skorzystać z jego cech aby oprogramować w prosty sposób podobne konstrukcje.

Właściwości prywatne

var Cons = function () {
    var private = 'jestem prywatna';
    this.getPrivate = function () { return private; };
};
 
var obj = new Cons();
obj.getPrivate(); // -> "jestem prywatna"
obj.private; // -> undefined

Dlaczego to działa? Wszystko opiera się o kolejny dziwoląg, czyli JavaScriptowego scope'a, a także o closures (BTW. fajne tematy na kolejne artykuły). W skrócie — funkcja zadeklarowana w innej funkcji ma dostęp do zmiennych dostępnych w swoim "rodzicu". Do tego mechanizm domknięć powoduje, że funkcja getPrivate "zapamiętuje" środowisko, w którym została zadeklarowana.

Właściwości statyczne

var Cons = function () {
    this.nonstatic = 'nie jestem statyczna';
};
Cons.static = 'jestem statyczna';
 
Cons.static; // -> "jestem statyczna"
Cons.nonstatic; // -> undefined

Chyba nic nie trzeba wyjaśniać. Konstruktor Cons jak każda funkcja jest obiektem, więc można dodawać mu właściwości, co robimy w czwartej linii.

Dla jasności dodam, że w identyczny sposób tworzymy prywatne i statyczne metody obiektów i klas.

Dostęp z obiektu do konstruktora

Każdy obiekt posiada właściwość constructor, która daje dostęp do konstruktora (porównywalne do Javowego Object.getClass()):

var Cons = function () {
    this.sth = 'sth';
};
 
var obj = new Cons();
obj.constructor; // -> function()
obj.constructor.toString(); // -> "function () { this.sth = "sth"; }"

A teraz niespodzianka. Dlaczego drugi sposób tworzenia konstruktorów (przez literał obiektu) uważam za gorszy? O to jeden z powodów:

var Cons2 = function () {
    return { sth: 'sth' };
};
var obj2 = Cons2();
var obj3 = new Cons2();
 
obj2.constructor; // -> Object()
obj3.constructor; // -> Object()
obj2.constructor.toString(); // -> "function Object() { [native code] }"
obj3.constructor.toString(); // -> "function Object() { [native code] }"

Zdefiniowaliśmy konstruktor, który teoretycznie działa jak ten z poprzedniego przykładu (zwraca obiekt o tej samej właściwości). Okazuje się jednak, że kiedy spróbujemy dostać się do konstruktora tego obiektu, to nie jest nim funkcja Cons2, a zwykła, natywna Object. Dla porównania:

({}).constructor.toString(); // -> "function Object() { [native code] }"

Co dalej?

Umiemy już posługiwać się obiektami. Umiemy też tworzyć ich konstruktory. Pora więc na dziedziczenie. O tym za czas jakiś w następnej części :)

GąsienicaOpublikowane na licencji CC przez Trufflepig.

Do tego czteroczęściowego (jak zapowiem, to może napiszę :) artykułu natchnęło mnie szkolenie z zaawansowanego JavaScriptu które, w miniony weekend, zorganizował we Wrocławiu Damian Ferrante Wielgosik. Już na początku chciałbym mu podziękować za wiedzę, którą się podzielił, ponieważ przed szkoleniem byłbym wstanie napisać tylko część tego artykułu.

JavaScript to dziwoląg

Tego nie da się ukryć. JavaScript należy do wąskiej grupy języków z rodziny ECMAScriptu (JS, ActionScript, E4X), którą cechuje to, że ich obiektowość oparta jest na prototypach. Oznacza to, że w JavaScriptcie nie istnieje pojęcie klasy. Nie oznacza to jednak tego, że w JavaScriptcie nie da się programować obiektowo z wykorzystaniem np. konkretyzacji, dziedziczenia, czy właściwości prywatnych. Da się. Co więcej — da się uzyskać dużo więcej funkcjonalności klasycznego języka obiektowego, choć, o czym na sam koniec, niekoniecznie jest po co.

Obiekt

W języku z obiektowością klasową wypada zacząć od definicji klasy. Tak przynajmniej zaczynali autorzy książek, które czytałem. W przypadku JavaScriptu mielibyśmy jednak pewien problem, bo klas tutaj nie ma. Trzeba więc zacząć od utworzenia obiektu. Oto najprostszy:

var obj = {};
typeof obj; // -> "object"

Tak, to w JavaScriptcie jest najprostszy obiekt. Zadeklarowany w przykładzie jest pusty i dziedziczy po obiekcie Object. Możemy sprawdzić to za pomocą poniższego kodu (jak działa i co to znaczy, o tym później):

var obj = {};
obj.__count__; // -> 0, działa tylko w Firefoksie
obj.__proto__.constructor; // -> Object()

Obiekt możemy również utworzyć za pomocą konstruktora Object(). Nie jest to jednak sposób polecany, ponieważ... zmienną Object można nadpisać. Lepiej pozostać przy literałach obiektów, które są bezpieczniejsze i ładniejsze.

var obj = new Object(); // -> Object
Object = 5;
var obj2 = Object(); // ->  TypeError: Object is not a constructor

Właściwości obiektu

Nic nam jednak z pustego obiektu. Pora dodać do niego dane i metody. Możemy skorzystać ze składni literału obiektu:

var obj = {
	text: 'Jestem obiektem',
	saySth: function () {
		alert(this.text);
	}
};

Bądź stworzyć pusty obiekt i dodać mu właściwości:

var obj = {};
obj.text = 'Jestem obiektem';
obj.saySth = function () {
	alert(this.text);
};

W obu przypadkach efekt będzie ten sam. Kiedy wywołamy obj.saySth(); dostaniemy alerta z tekstem Jestem obiektem.

Jeszcze słowo wtrącenia o literałach. Trzeba uważać na przecinek po ostatniej właściwości. Firefox i chyba wszystkie normalne przeglądarki trzymają się specyfikacji ECMAScript 5 pkt 11.1.5 (bądź to specyfikacja trzyma się tych przeglądarek ;), a kochany IE6, który zawsze musi być inny ("inny nie znaczy gorszy" w tym wypadku nie działa ;) trzyma się specyfikacji ECMAScript 3 pkt 11.1.5. Tak więc przecinek po ostatniej właściwości zostanie zaakceptowany przez przeglądarki z rodziny normalnych, zaś IE6 wywali cichaczem błąd. Nie wiem co prawda jak sprawa wygląda w IE7 i IE8, ale radzę po prostu tego przecinka nie stawiać. Koniec dygresji.

W Javie, czy PHP metoda obiektu jest czymś zupełnie innym niż jego właściwość. W JavaScriptcie metoda jest po prostu funkcją przypisaną do właściwości obiektu. Widać to najlepiej w trzeciej linii ostatniego przykładu: obj.saySth = function () {};. Wynika to z tego, że w JavaScriptcie funkcja też jest obiektem. Dzięki temu możemy ją przypisywać do zmiennych, zwracać w innych funkcjach, czy... wywoływać na niej metody. Ta właściwość JavaScriptu jest naprawdę potężna i myślę, że język ten sporo teraz zyskał w oczach osób lubiących Rubiego, Pythona, czy jakiś język funkcyjny :).

Uwaga na this

typeof obj.saySth; // -> "function"
var sayNothing = obj.saySth;
typeof sayNothing; // -> "function"
sayNothing(); // -> alert undefined

Z pierwszych trzech linii widać, że możemy sobie dowolnie operować funkcją. Czwarta może być zaskoczeniem. Dlaczego this.text === undefined? Zauważcie, że funkcja sayNothing nie została wywołana w kontekście obiektu obj, tylko w kontekście... no właśnie — czego? Żeby to sprawdzić dodajmy dwie linie kodu do poprzednich przykładów:

window.text = 'Jestem globalnym scopem';
sayNothing(); // -> alert "Jestem globalnym scopem"

Teraz widać, że funkcja sayNothing(); wywołana jest w globalnym kontekście. Możemy ją także wywołać w odpowiednim kontekście używając metody call() działającej na funkcji (tak jak pisałem wcześniej — funkcja też jest obiektem, więc ma też swoje metody):

sayNothing.call(obj); // -> alert "Jestem obiektem"

Więcej na ten temat u Ferrante w "this" w JavaScript.

Jeszcze trochę o obiektach

Do właściwości obiektu możemy się również dostać za pomocą operatora [], czyli w taki sposób jak do elementów tablicy. Jest to przydatne kiedy chcemy uzyskać właściwość, której nazwa trzymana jest w zmiennej:

var obj = { a: 'A', b: 'B', m: function () { return 'M'; } };
 
obj['a']; // -> "A"
var name = 'b';
obj[name]; // -> "B"
obj['m'](); // -> "M"

Po właściwościach obiektu można też iterować za pomocą konstrukcji for (i in obj). Trzeba jednak uważać, bo, w zależności od tego jakim obiektem dysponujemy, możemy dostać niespodziewane (przynajmniej na razie) rezultaty. Póki co jednak wystarczy nam:

var obj = { a: 'A', b: 'B', c: 'C', d: 'D' };
for (i in obj) {
	alert('obj[' + i + '] = ' + obj[i]);
}

Obiekty można też zagłębiać. Jest to własność oczywista, ale wypada dla jasności o niej wspomnieć.

var obj = {
	obj: {
		obj: {
			hidden: 'Zonk'
		}
	}
};
obj.obj.obj.hidden; // -> "Zonk"

Co dalej?

Samym obiektem programista żyć nie może. Klas jednak w JavaScripcie nie uświadczymy, trzeba więc wymyślić coś innego. Z pomocą przyjdą nam konstruktory obiektów, ale zrobią to dopiero w następnej części :).

Poza tym w trzeciej części mam zamiar napisać o prototypach, czyli JavaScriptowym dziedziczeniu. W czwartej chciałbym zebrać wszystko do kupy, pokazać może jakieś wzorce i napisać kilka uwag o programowaniu w JavaScriptcie. W zasadzie to uwagi mam już napisane, bo zacząłem od końca :D

Przegląd prasy

1 komentarz

Zdjęcie prasy hydraulicznej Przez mój czytnik i mojego blipa przewija się masa linków. Część ze znalezionych informacji dodaję do swojego del.icio.us, jednak mimo to o tych najważniejszych / wartych najwięcej uwagi czasem zapominam. Pomyślałem więc, że będę publikował co jakiś czas najciekawsze moim zdaniem znaleziska z zakresu web developowania i usability. Raz, że samemu będzie mi łatwiej do tych informacji wrócić, dwa, że może komuś zaoszczędzę trochę czasu i podsunę coś czego jeszcze nie czytał.

Wiele z poniższych linków macie szansę znać, wiele to starsze informacje, ale to pierwszy wpis w tym cyklu (tak, jeśli będę miał o czym pisać, to może zacznę to robić regularnie :) - następne będą aktualniejsze i zgrabniejsze. Na dodatek aby znaleźć te linki niestety musiałem przeglądać historię i wiele ciekawych rzeczy mogłem pominąć.

Usability vel użyteczność

Łebowe i niełebowe programowanie

Inne

Póki co na tym koniec. Od teraz co ciekawszy znaleziony link będę sobie odkładał, więc następne wydanie przeglądu prasy będzie aktualniejsze.

Rys historyczny, można pomijać

Tak, wiem, że istnieje coś takiego jak debugger :). Nigdy się jednak z żadnego porządnie nie nauczyłem korzystać. Po trochu dlatego, że wymaga to zazwyczaj poświęcenia dodatkowego czasu na naukę nowego narzędzia, po trochu dlatego, że najwięcej kodu tworzę w PHP i JS w gVimie, który chyba nie posiada żadnego zintegrowanego narzędzia, ale głównie dlatego, że to często armata na komara. Niejednokrotnie wystarczy podejrzeć przecież zawartość jednej zmiennej aby rozwiązać problem. I tak prawdopodobnie każdy programista nauczył się "echowania" (kłania się zapewne PHP ;).

Problem pojawia się jednak kiedy zaczynamy tworzyć w Javascriptcie, który to nie posiada żadnego wyjścia, jakieś proste skrypciki. Pierwszym pomysłem jaki przychodzi do głowy jest wykorzystanie funkcji alert(). Często to dobre rozwiązanie, choć mało wygodne - wyskakujące okienko bywa naprawdę denerwujące. Szczególnie jak zaczyna pokazywać się coraz częściej i w większych ilościach. Dodatkowo, Firefox z którego korzystam, ma tę niezrozumiałą dla mnie własność, że kiedy wyskoczył jakiś alert nie dopuszcza mnie do okna przeglądarki. Co więcej - nie pozwala (a przynajmniej mi się nigdy nie udało) wyłączyć Javascriptu z poziomu tegoż okienka (co potrafi np. Opera). Kończy się więc to killem przeglądarki kiedy jakiś dowcipniś wrzuci alert("joke"); w niekończącą się pętle. A samemu podczas "echowania" też zdarzyło mi się zabić takim alertem, więc trzeba uważać :) (setInterval(), czy niefortunne powiązanie z jakimś zdarzeniem).

W związku z pytaniem, które ktoś rzucił dzisiaj na blipa zacząłem zastanawiać się czy nie ma lepszego rozwiązania. Pierwszy pomysł jaki mi przyszedł do głowy to napisanie prostej klasy Console, która będzie dodawała swoje okno na stronie i za pomocą jakiejś metody out(), push(), czy jakiejś pozwalała ładnie wypisywać zawartość danej zmiennej. Szybko jednak okazało się, że twórcy Firebuga wpadli na ten pomysł pierwsi :).

Konsola Firebuga, część właściwa wpisu

Konsola Firebuga udostępnia nam kilka ciekawych funkcjonalności. Pierwszą z nich jest console.log(), która pozwala formatować wyjście na kilka sposobów:

console.log("Ten kod powinien się wykonać");
 
var foo = "bar", x = 12.34, i = 122;
 
console.log("foo: %s, (int) i: %d, (float) x: %f", foo, i, x);
 
function Kopytko() {
	this.palec1 = 11.12;
	this.palec2 = "qń";
};
kopyto = new Kopytko();
console.log(kopyto);
console.log(kopyto.brak);

Przykład działania console.log w Firebugu

Jednak to nie wszystko. Możemy także pokolorować i nadać znaczenia naszej radosnej twórczości. I tak mamy dostępne metody console.debug(), console.info(), console.warn(), console.error().

Przykład formatowania informacji na konsoli Firebuga

Dodatkowo, przydatne (np. przy pracy z Ajaksem, czy manipulacjach w drzewie DOM) może się okazać również drukowanie HTMLa oraz XMLa. Służy do tego metoda console.dirxml(DOMElement). Wyświetla ona drzewo znane nam z zakładki "HTML" Firebuga.

Przykład działania console.dirxml() w Firebugu

To chyba na tyle o "echowaniu" na konsolę Firebuga. Więcej informacji możecie znaleźć w Console API oraz Rejestrowaniu Javascriptu (damn - nie mogę lepszego tłumaczenia znaleźć :). I tak z poziomu kodu można mierzyć czas wykonania bloku instrukcji console.time("name"); {..} console.timeEnd("name");, czy tworzyć asercje (a jak to się tłumaczy?).

Rys historyczny cz. 2, też można pominąć

Wpis ten napisałem głównie dla ludzi, którzy tak jak ja, jakimś cudem przeoczyli tę funkcjonalność Firebuga. Nie mylili się Zergu i mcv mówiąc, że o rzeczach oczywistych się nie pisze bo są... oczywiste. Jak jednak często się okazuje - nie dla każdego :). A właśnie taka demotywująca myśl spowodowała, że od tak dawna nic nie pisałem.

Wpis ten ukazał się w okrojonej (mniej osobistej) formie na moim blogu firmowym