Architektura aplikacji JavaScript: Wstęp

Architektura aplikacji JavaScript to jeden z najczęściej przejawiających się tematów w dyskusjach na temat tego języka. Jest to chyba też jedno z najmniej opisywanych zagadnień w ogóle, zupełnie nieproporcjonalnie do ilości zadawanych pytań. Te formułujemy najczęściej, kiedy musimy napisać dużą aplikację. Równie regularnie głos na temat architektury podnoszą programiści, którzy zaczęli pisać w JS po odejściu od Javy, Pythona czy innych obiektowych języków. Pojawia się rozczarowanie połączone z zaskoczeniem. Jak zatem powinno się pisać w JavaScript?

Żeby mówić o architekturze w JS, należy zrozumieć specyfikę tego języka. Nie mamy tutaj klas, dziedziczenie można uzyskać na kilka sposobów, jednocześnie możemy spokojnie pisać, korzystając z paradygmatów programowania funkcyjnego, nie tracąc na efektywności naszych programów. Niestety, bez zrozumienia istoty closures, prototype, kontekstu uruchomienia funkcji nie możemy skutecznie przejść do zagadnienia architektury.

Jeśli nie znasz tych pojęć, pozwól, że krótko je przypomnę. Pamiętaj jednak, że JavaScript jest na tyle specyficznym językiem, że ich zrozumienie może zająć więcej czasu niż przeczytanie tego artykułu. Praktyka czyni mistrza!

Przede wszystkim przeglądarki normalne przeglądarki nie wymyślają JavaScriptu po swojemu. Jest to język oparty na specyfikacji o nazwie ECMAScript. Również ActionScript (tak, to, co będzie niedługo wyparte z webu przez HTML5) opiera się na ECMA. Jeśli coś wywoła w Tobie sążniste ale o co tu chodzi, majster?!, to na pewno w powyższym dokumencie znajdziesz odpowiedź na swój programistyczno-egzystencjalny ból.

Podstawą JS są funkcje, zresztą jak w każdym języku. Możemy je zdefiniować tak:

var suma = function(a, b) {
	return a + b;
};

Taki kod, a konkretnie funkcja o nazwie suma pozwoli nam zwrócić wynik dodawania dwóch liczb, które podamy do funkcji:

suma(5, 10); // zwróci 15

To podstawy. By mówić o dobrej architekturze, musimy ją oprzeć na jak najbardziej skalowalnych i tzw. “reużywalnych” komponentach. Zapewne więc chciałbyś wiedzieć, jak wykorzystać funkcje do tworzenia obiektów, skoro w JavaScript nie ma klas ani niczego takiego. Jest kilka sposobów, jeden lepszy, drugi gorszy. Pierwszy z nich:

var DataManager = function() {
	this.add = function() {};
	this.get = function() {};
};

Prosta funkcja DataManager, która powinna pomóc tworzyć obiekty do zarządzania danymi. Jak rzeczywiście stworzyć z tego obiekt?

var manager = new DataManager();

Nic prostszego! Zapewne widzisz tutaj analogię do innych języków, gdzie new również pomaga tworzyć nowe obiekty. W ten oto też sposób, DataManager możemy nazwać konstruktorem obiektu (w przyszłości zapewne będę używał tej nazwy na przemian ze słowem “funkcja“).

A jakby to wyglądało w JAVA?

public class DataManager {
	public void add() {}
	public void get() {}
}
// [...]
DataManager manager = new DataManager();

W PHP?

class DataManager {
	public function add() {}
	public function get() {}
}
$manager = new DataManager();

Wracając do JS… Dzięki temu, że stworzyliśmy dwie metody: add oraz get, nasz obiekt manager będzie mógł ich użyć:

manager.add("blog", "ferrante.pl");
manager.get("blog");

Ten kod oczywiście nic nie robi, bo funkcje (vel metody) add oraz get zdeklarowaliśmy jako puste:

this.add = function() {};
this.get = function() {};

Dodajmy do nich coś, nie ma znaczenia co, na razie niech to będą komunikaty dla użytkownika (oczywiście w realnym świecie jest to mało prawdopodobne):

var DataManager = function() {
	this.add = function() {
		alert("dodaję!");
	};
	this.get = function() {
		alert("zwracam!");
	};
};

Jak widzisz, this pomógł nam zrobić niesamowity trick (szacuneczek!), by te dwie metody były dostępne dla każdego nowo utworzonego obiektu (u nas manager).

this to jednak zguba w JS. Pomińmy new przy tworzeniu nowego obiektu:

var manager = DataManager();

A teraz spróbujmy wywołać nasze metody.

var manager = DataManager();
manager.add("blog", "jsnews.pl");

Ups? Dostajemy błąd, a przyczyna leży w tym, że do manager tym razem nic się nie przypisało, więc bez sensu byłoby wywoływać jakiekolwiek metody na nim. Dlaczego? Otóż bez new wywołujemy tylko funkcję (bez zamiaru tworzenia obiektów). Jeśli ona nic nie zwraca, to też to “nic” jest przypisywane do manager. To “nic” w JS nazywa się undefined – oznacza, że OK, przypisaliśmy coś, lecz ta wartość nie została zdefiniowana. Każda funkcja zwraca undefined, jeśli nie zdefiniujemy return na jej końcu.

var suma = function() {};
suma(a, b); // undefined
var suma = function(a, b) {
	return a + b;
};
suma(10, 20); // 30

Jak sprawdzić, czy coś rzeczywiście jest niezdefiniowane? Pomaga nam w tym konstrukcja typeof:

var suma = function() {};
var wynik = suma();

if (typeof wynik === "undefined") {
	alert("funkcja nic nie zwróciła!");
}

Jak widzisz, typeof zwraca stringa, więc jego wynik porównaliśmy do “undefined”. Typeof to jeden sposobów na to, by sprawdzić jakiego typu jest zmienna. Zainteresuj się tym w wolnym czasie (możesz też o tym przeczytać w mojej prezentacji varjs.com).

Wracając jednak do naszego przykładu z DataManager. Zrobiliśmy coś takiego:

var manager =  DataManager();
manager.add("blog", "jsnews.pl");

I dostaliśmy błąd.

Ale! Poniższa linijka, już bez jakiejkolwiek obstawy, działa:

var manager =  DataManager();

Nie powoduje ona żadnych komplikacji. Co więc stało się z rzeczami, które przypisaliśmy do this? W sensie, w tym miejscu:

var DataManager = function() {
	this.add = function() {
		alert("dodaję!");
	};
	this.get = function() {
		alert("zwracam!");
	};
};

Otóż, drogi Czytelniku, add oraz get są teraz zmiennymi globalnymi! Na przykład:

<script>
	var DataManager = function() {
		this.add = function() {
			alert("dodaję!");
		};
		this.get = function() {
			alert("zwracam!");
		};
	};

	var manager = DataManager();
</script>

<script>
	add("cos tam", "cos tam"); // dodaję!
	get("cos tam"); // zwracam!
</script>

Tak, add i get są dostępne w każdym ze skryptów na stronie (no chyba, że jakiś je nadpisał…). Zupełnie tak, jakbyś zrobił:

<script>
	var add = function() {
		alert("dodaję!");
	};
	var get = function() {
		alert("zwracam!");
	};
</script>

Ewidentną zdradę popełnioną przez this dokumentuje poniższy kod:

var fn = function() { return this; };
window === fn();

Tak jest, thisem przy wywołaniu funkcji jest domyślnie obiekt globalny, który w środowiskach przeglądarkowych nazywa się window. Jest to zmienna jak każda inna, dodatkowo jest obiektem, więc można coś do niej przypisywać i używać rzeczy już przypisanych po kropce (jak to w obiektach bywa) np. window.alert(“yeah!”);.

Przedstawiłem Ci jeden ze sposobów tworzenia obiektów, jak widzisz nie zawsze bezpieczny (szczególnie, jeśli mamy do czynienia z mniej doświadczonymi programistami JS w zespole). Dodatkowo, teraz już wiesz, dlaczego Douglas Crockford nie lubi new. Jakie mamy alternatywy?

Możemy zwracać gotowy obiekt w każdej z funkcji. W naszym przypadku będzie to tak:

var DataManager = function() {
	return {
		add: function() {
			alert("dodaję!");
		},
		get: function() {
			alert("zwracam!");
		}
	};
};

Proste i klarowne! Zauważmy, że zawsze – bez znaczenia, czy użyliśmy new czy nie – dostajemy nowy obiekt:

var manager = new DataManager();
manager.add("blog", "ferrante.pl");
manager.get("blog");

var anotherManager = DataManager();
anotherManager.add("blog", "jsnews.pl");
anotherManager.get("blog");

Nie ma też żadnych wycieków do globalnej przestrzeni:

var manager = DataManager();
add("cos tam"); // bląd!

Jak to się stało? Wszystko załatwił tzw. object literal, czyli to coś w klamrach, z dwukropkami oraz przecinkami. O, to!

return {
	add: function() {
		alert("dodaję!");
	},
	get: function() {
		alert("zwracam!");
	}
};

Najprostszy object literal wygląda tak:

var obj = { 
	foo: function() { 
		alert("boom!"); 
	} 
};
obj.foo(); // "boom!"

Pusty obiekt:

var obj = {};

Lub obiekt posiadający własności nie będące funkcjami:

var obj = {
	foo: 1,
	bar: 2
};
alert(obj.foo); // 1

Można też umieścić obiekt w obiekcie:

var obj = {
	foo: {
		method: function() { alert("foo method!"); }
	}
};
obj.foo.method(); // foo method!

W ten sposób możemy definiować nowe obiekty bez używania konstruktora. Szybko i łatwo od razu jest gotowy. Pamiętaj, by jego składowe definiować po przecinku, więc jeśli mamy np. trzy metody, to powinny pojawić się 2 przecinki:

var obj = {
	metoda1: function() {},
	metoda2: function() {},
	metoda3: function() {}
};

Jak widać, jest to zwykły obiekt, a literal jest dlatego, że definiujemy go “od ręki”, wypisując wszystkie składowe.

Jeśli tworzyłeś do tej pory obiekty w ten sposób: var obj = new Object – szybko o tym zapomnij. Nieoptymalne, niebezpieczne, wolne.

Jest jeszcze trzeci sposób, jak ugryźć obiekty. Otóż obiekty poprzez zdefiniowanie i użycie konstruktorów można dostać jeszcze w inny sposób, niezwiązany z this. Będzie tutaj potrzebna mityzowana własność prototype, którą posiada każda z funkcji. Tak, tak, np. taka:

var fn = function() {};

Czy taka:

var foobar = function() {
	return "foobar";
};

Każda, no.

Dostajemy się do prototype następująco:

foobar.prototype

Jak widać nie ma tutaj zbędnej filozofii – foobar to zmienna wskazująca na funkcję, choćby weźmy ją z przykładu parę linijek wyżej. Możesz też sprawdzić, że coś takiego istnieje używając np. włączonego Firebuga i jego konsoli, a konkretnie metody console.log:

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

Wykorzystajmy też swoją wiedzę o typeof i zobaczmy jakiego typu jest własność prototype:

var fn = function() {};
alert(typeof fn.prototype); // "object"

Tak, prototype to obiekt, który może jakieś składowe przechowywać, można coś do niego przypisać lub coś z niego usunąć.

Przepiszmy teraz nasz przykład z DataManager używając powyższej konstrukcji:

var DataManager = function() {};
DataManager.prototype.add = function() { alert("dodaję!"); };
DataManager.prototype.get = function() { alert("zwracam!"); };

A teraz stwórzmy jakiś obiekt na podstawie konstruktora DataManager:

var manager = new DataManager();
manager.add(); // dodaję!
manager.get(); // zwracam!

Możesz być zaskoczony, że to działa. A jednak. Zero this! Co się dzieje? Otóż .prototype to własność, która jest kopiowana do każdego z nowo utworzonych obiektów na podstawie konstruktora, do którego to .prototype należy. Jeśli więc coś jest w DataManager.prototype to zostanie to skopiowane do jakiegokolwiek obiektu, który utworzymy tak: var obj = new DataManager();. Można więc założyć, że .prototype to obiekt bazowy, z którego korzystają inne obiekty.

Warto jednak uważać. .prototype jest kopiowany przez referencję, więc w każdym obiekcie jest ten sam. Proste sprawdzenie:

var manager = new DataManager();
var anotherManager = new DataManager();
manager.add === anotherManager.add; // true

Porównujemy dwie funkcje należące do dwóch różnych obiektów stworzonych na podstawie konstruktora DataManager. Wynika na to, że są to te same funkcje zapisane w jednym i tym samym obszarze pamięci.

Dokładniej ten problem możemy zbadać na innym przykładzie. Jak powiedziałem, każdy obiekt kopiuje sobie przez referencję obiekt .prototype z konstruktora (tak, tak – tutaj manager i anotherManager kopiują sobie DataManager.prototype) i gdzieś wewnątrz go przechowuje. W niektórych przeglądarkach możemy dostać się do tej własności. A mianowicie jest nią obj.__proto__:

var manager = new DataManager();
var anotherManager = new DataManager();
manager.__proto__ === anotherManager.__proto__; // true

W przyszłości, używając JS opartego na ECMAScript w piątej edycji, będziemy mogli do tego użyć czegoś takiego, jak getPrototypeOf:

manager.__proto__ === Object.getPrototypeOf(anotherManager); // true

Nie jest to jednak w tym momencie wiedza niecierpiąca zwłoki.

Głównym zagrożeniem, może okazać się coś następującego:

var fn = function() {};
fn.prototype.foo = function() { alert("foo"); };

var obj = new fn();
obj.foo(); // foo

fn.prototype.foo = function() { alert("bar"); };
obj.foo(); // bar

Każda zmiana w prototypie konstruktora ma wpływ na utworzone już obiekty – z łatwością podmieniliśmy funkcję foo.

Podsumowując, poznaliśmy właśnie kolejny ze sposobów na definiowanie klasopodobnych konstruktorów oraz uzyskiwanie z nich obiektów. Główną zaletą .prototype jest to, że własności są kopiowane przez referencję, co za tym idzie oszczędzamy pamięć, a także łatwe dziedziczenie, o czym w kolejnych artykułach. Natomiast nadal należy pamiętać o new. Do tego o wiele trudniej o zaimplementowanie metod prywatnych (zauważ, że do tej pory operowaliśmy tylko na publicznych). O nich, o closures i innych w kolejnym odcinku.

Komentarze

1

Wow, jakie kompendium. ;] JavaScript faktycznie potrafi dostarczyć wielu wrażeń, czasem pozytywnych, czasem negatywnych, ale możemy się przynajmniej cieszyć, że jest to de facto jedyna możliwość i standard w dynamicznych operacjach na stronach internetowych. ;]

2

No w końcu jakiś wpis czysto o JS – bo ostatnio już w robocie słyszałem, że “coś ferrante przestał pisać o JS, tylko jakieś konferencje, wywiady itd” ;)

Rozumiem, że to początek jakiegoś większej serii wpisów?

3

Zacząłem się bawić z obiektami z nudów, a teraz bez mojego obiektu-skrzynki narzędzi ciężko mi żyć :)

Ciekawy artykuł, czekam na ciąg dalszy

4

Tak, powinno byc kilka odcinków.

5

To jeszcze dwa odcinki wstępu i dojdziemy do architektury aplikacji :)

Spoko, czekam na kolejne.

6

Nie mogę zakładać, że każdy ma JS w małym paluszku. Różne doświadczenia podpowiadają mi, że dobrze robię ;-)

7

@ferrante: To założenie jest fałszywe, nawet jeśli masz pewność, że rozmawiasz z ludźmi na odpowiednim poziomie. Po prostu z takich informacji nie korzysta się na co dzień, “z linijki na linijkę” i po dniach, tygodniach, miesiącach w końcu to wylatuje z głowy. Wtedy po raz kolejny wpisujemy w Google “architektura aplikacji javascript wstep”, żeby sobie pewne rzeczy przypomnieć. ;]

8

Jedyne co mnie zdziwiło to “Najprostszy object literal”.
przeciez podany obiekt z metoda wywolujaca inna funkcje nie jest najprostszy…

object literal to tez:
{}
{a:1}
ktore sa prostsze i bardziej minimalistyczne ;-)

xbojer
9

Jeszcze nie widziałem tak krótko i przystępnie wyjaśnionych prototypów.

Z zastrzeżeń: W kilku miejscach tłumacząc banały zakładasz po cichu, że czytelnik jest świadomy pewnych kruczków, które nie są oczywiste dla początkujących JSowców.

np.
używanie alerta, a później nagłe przejście na firebuga.
lub
manager.add === anotherManager.add; // true
Mało który początkujący uzna to za przekonujący dowód, że to dwie identyczne referencje. “bo dlaczego dwie takie same funkcje nie miałyby być równe?”

Początek może aż nazbyt prosty, ale ogólnie bardzo przydatny artykuł. Zgadzam się z tym co napisał @Tomasz – przydaje się czasem znów zajrzeć do takich tekstów, żeby nie klepać wszystkiego według tego samego schematu, tylko dobrać odpowiednią implementację obiektowości do zastosowania.

Czekam na closures. Ciekawe czy robię je tak samo :)

10

Panowie, nie ocierajmy się o absurd, jeśli chodzi o ewentualne uwagi co do artykułu. Object literal miał mieć funkcję, by pokazać, jak się ją uruchamia, ale fakt, dodałem też przykłady z pustymi i zagnieżdżonymi obiektami.

Co do === – to są podstawy programowania, nie chciałem tłumaczyć od podstaw wszystkiego, zostawiając otwartą furtkę do riserczu dla Czytelnika, który nie programował w niczym indziej.

Ten wstęp jest docelowo dla osób, które mają jako takie pojęcie o programowaniu, aczkolwiek chciałem też zadbać o tych startujących od zera i myślę, że jako tako to się udało.

11

Pytanie: dlaczego w poniższej konstrukcji ‘this’ wskazuje na zwracany object literal? Object literal chyba “nie zawiera w sobie” this tak, jak funkcja?

function f() {
	return {
		state: 0,
		show: function() {
			this.state = 1;
		}
	}
};
Marcin
12

Bardzo dobrze opisane.

13

W momencie deklaracji funkcji, czyli w kodzie, ktory napisales, this nie bedzie wskazywal na nic, dopiero przy wlaczeniu show() bedzie szukal dla niego obiektu:

var obj = f();
obj.show();
alert(obj.state); // 1

Aczkolwiek wiaze sie to zagrozeniami a’la:

var obj = f();
var fn = obj.show;
fn();
alert(obj.state); // 0
alert(state); // 1

Pisalem o tym tutaj: http://ferrante.pl/frontend/javascript/this-w-javascript/

Warto zapamietac po prostu, ze domyslnie this jest “lotny” i zalezy od kontekstu uruchomienia metody (a wiec tego, co jest przed nia przed kropka).

14

Istnieje w takim razie, “bezpieczny” sposób odwoływania się do właściwości w metodach? {that: this} w takim razie nie pomoże.

Marcin
15

W przypadku wyzej mozesz zrobic:

show.call(obj);

Ale to takie skroty sa.

Mozesz zrobic tez tak:

function f() {
	var obj = {
		state: 0,
		show: function() {
			obj.state = 1;
		}
	};
	
	return obj;
};

Mialem o tym właśnie pisać w kolejnym odcinku, ale sprzedam Ci jeszcze przykładowy kod do analizy, jak można w ogóle zrezygnować z thisa:

var f = function() {
	var state = 0;
	var show = function() {
		state = 1;
	};

	return {
		show: show
	}
};
16

JS jest lepszy niż plastelina. Dzięki!

Marcin
17

@Marcin: Jeśli chodzi o możliwość “wyginania” wedle uznania, to tak – plastelina może mu pozazdrościć. ;]

18

Dobry artykuł, w końcu jakiś konkret ;]
Czekam na kolejne podobne, bo aż miło się czyta.

19

Bardzo konkretnie i przystępnie napisany tekst. Również czekam na kolejne :).

20

Damian, wreszcie! :D Już Cię tyle męczyłem, żebyś napisał coś o architekturze. Co prawda myślałem o zdecydowanie wyższym poziomie, ale od czegoś trzeba zacząć :P

Trochę już zostało w komentarzach powiedziane, ale mam jeszcze jedną wskazówkę od siebie. A mianowicie poradę na zapominalstwo względem new i wyciekanie do globalnego scope’a. Z własnego doświadczenia wiem, że jest to jeden z najgorszych do tropienia błędów, ponieważ najczęściej wszystko działa, dopóki nie utworzymy drugiego obiektu z tego samego konstruktora :D

W tej chwili na szczęście jest już na to lekarstwo – a mianowicie strict mode w Fx4.

Dla przykładu:
var fn = function () {
this.x = 1;
};
fn();
console.log(x); // -> 1

Ale:
'use strict';
var fn = function () {
this.x = 1;
};
fn(); //error "this is undefined"
console.log(x);

Tak więc dla mnie minimum to pisanie na devie w strict modzie i testowanie pod Fxem, bo można dużo swoich błędów wyłapać (drugi najpopularniejszy również – błędy w nazwach zmiennych powodujące również bajzel w globalnym scope’ie).
Używam też strict mode’a na produkcji kiedy kod musi działać tylko pod Fxem i Chromem (ten drugi jeszcze nie ma wsparcia dla strict mode’a). Pojawiają się głosy, żeby uważać, bo strict mode zmienia trochę semantykę i może spowodować, że coś nam będzie działać pod Fxem, a pod np. właśnie Chromem nie, ale jakoś na nic jeszcze nie trafiłem. Jeśli się na siłę nie szuka miejsc gdzie ta semantyka się różni, to wszystko śmiga.
Możliwe też że nie byłoby problemu również z innymi przeglądarkami. To znaczy, że kod strict mode’owy ładnie się degraduje w śmIEciach i innych muzycznych przeglądarkach, ale nie testowałem, więc nie wiem.

Taka mała uwaga jeszcze – brakuje mi coś nagłówków w tekście :)

21

dziękuję. post rewelacja.

Timi
22

fajny zbiór podstaw, brawo.
większość z nich już miałem okazję przerabiać ;-)

23

Szacun :) Tego właśnie mi brakowało. Czekam niecierpliwie na kolejne części!!!

helmut
24

Nie anotherManager.getPrototypeOf() tylko Object.getPrototypeOf(anotherManager) :)

25

Oj, fakt, poprawiłem.

26

No ferrante następny dobry artykuł. Tak trzymaj :)

27

Bardzo dobry artykuł, mam nadzieje ze będą kolejne o architekturze dużych aplikacji javascrip :)

Paweł Machowski
28

Wreszcie jakieś konkrety, czekam z niecierpliwością na kolejne artykuły bo temat jest ciekawy i przymierza się do osób bardziej zaawansowanych w js.

dmp
29

Moim zdanie popelniles Autorze blad, piszac o literalach obiektowych na poczatku: w rzeczywistosci to nie sa zadne obiekty (to znaczy oczywiscie sa, ale nie w rozumieniu np. C++ – czyli jako konkretyzacje pewnej klasy; w JS klas nie ma – por. z Self, Io lub Pike) a tablice asocjacyjne czy slowniki. Popatrzmy na porownanie tych struktur w Pythonie i JS:


#python
d = {}
d[1] = "omg"
d["omg"] = 1
#js
d = {}
d[1] = "omg"
d["omg"] = 1

Uderzajace podobienstwo, prawda? Notacja z kropka jest tylko skrotem dla nawiasow kwadratowych (albo odwrotnie). To, ze przy okazji obiekty w JS (tzn. np. {}) sa rowniez prawdziwymi obiektami to ani nic niezwyklego (dict w pythonie tez nim jest) ani specjalnie odkrywczego, skoro w JS wszystko jest obiektem (np. ‘literal calkowity jeden’, tj. 1, tworzy po prostu obiekt Number. Sprawdzcie: 1..toSource() ).

W rzeczywistosci to nie zorientowanie obiektowe, a wlasnie closures jest prawdziwym “silnikiem” i “moca” tego jezyka – prototypy to mily dodatek, ale tylko dodatek. Dlatego uwazam, ze to wlasnie od closures powinno sie zaczynac opis “prawdziwego” JSa. Wiem, ze to wiesz, Autorze, bo pokazujesz to w 15 komentarzu :) Mowie tylko, ze moim zdaniem kolejnosc prezentacji tych cech jezyka powinna byc odwrotna.

Wykorzystanie calej mocy closures wymaga wiele praktyki, a nie tylko pobieznego zrozumienia. Najlepiej poswiecic kilka miesiecy na nauke jakiegos jezyka funkcyjnego (niekoniecznie czysto – ja katuje obecnie Racket, ale dobra jest Scala (jak ktos nie ma alergii na JVM) albo F# (jak ktos nie ma alergii na .NET) ). Moj ulubiony przyklad, ktory pokazuje roznice miedzy OOP a funkcyjnym jest taki prosty generator/akumulator:


var f = function ( do_czego ) {
return function( ile_dodac ){
return do_czego += ile_dodac;
}
}

Wykorzystujemy ten kod – to czysto teoretyczny przyklad, oczywiscie – mniej wiecej w taki sposob:


var acc = f(0); // startujemy od zera
acc( 5 ); // == 5
acc( 5 ); // == 10

Dzieki closure mamy cos, co zupelnie spokojnie moglibysmy nazwac obiektem – tylko, ze zapisane zwiezlej i (jak juz sie czlowiek przyzwyczai) rownie czytelnie, jak przy uzyciu “klasycznego (dla JS) obiektu” :


var f = function( do_czego ){
this.do_czego = do_czego;
this.add = function ( ile_dodac ) {
return this.do_czego += ile_dodac;
}
}
var obj = new f( 0 );
obj.add( 5 ); // == 5
obj.add( 5 ); // == 10

Roznica nie jest wielka, zreszta nie chce mowic, ze ktores z tych podejsc jest jedynie sluszne, bo to nieprawda. Wedlug niektorych ludzi* closures sa potezniejsze, niz obiekty w dowolnej odmianie, ale nie jestem pewien, czy ich argumenty do mnie trafiaja. Jednak closures sa na tyle “obce” poczatkujacym programistom (powiedzmy ponizej pieciu dobrze/bardzo dobrze znanych jezykow) ze warto o nich pisac duzo wiecej i czesciej, niz o obiektach, nawet takich prawdziwych (bez klas) jak w JS.

Pozdrawiam, CJI.

cji
30

gwiazdka z poprzedniego komentarza: Paul Graham. Rowniez od niego zaczerpnalem przyklad z akumulatorem.

cji
31

@cji: Bardzo lubię jak ktoś wykracza poza ramy podstawowego JS-a i pokazuje wzorce / idee które pasują do tego języka i w tę stronę mocno biłeś, jednak z kilkoma rzeczami zgodzić się nie mogę :)

w rzeczywistosci to nie sa zadne obiekty (to znaczy oczywiscie sa, ale nie w rozumieniu np. C++ – czyli jako konkretyzacje pewnej klasy; w JS klas nie ma – por. z Self, Io lub Pike) a tablice asocjacyjne czy slowniki

Jedna rzecz to paradygmat programowania obiektowego (http://pl.wikipedia.org/wiki/Programowanie_obiektowe), drugie to dziedziczenie (i w ogóle programowanie) oparte na klasach. To dwie niezależne, choć mocno związane idee (http://pl.wikipedia.org/wiki/Programowanie_obiektowe#Podzia.C5.82). Tak więc to w pełni są obiekty, że nie w rozumieniu C++ – jak najbardziej, klas nie ma – prawda, ale że są to słowniki, to nie mogę się zgodzić. Owszem – mają taką funkcjonalność, ale są czymś więcej. Mają swój kontekst this i to jest podstawowa różnica.

W rzeczywistosci to nie zorientowanie obiektowe, a wlasnie closures jest prawdziwym „silnikiem” i „moca” tego jezyka – prototypy to mily dodatek, ale tylko dodatek. Dlatego uwazam, ze to wlasnie od closures powinno sie zaczynac opis „prawdziwego” JSa.

Też się nie zgadzam :) Clousures jest potężne, ale potężniejsza, bądź równie potężna jest obiektowość JS-a, która przecież jest “statutowa” dla tego języka (patrz: http://code42.pl/2011/01/08/delegacja-i-funkcje-generyczne-w-javascript/#comment-973). Wielką moc ma język w którym minimalnym wysiłkiem możesz spełnić główne kryteria programowania obiektowego, czyli (za: http://pl.wikipedia.org/wiki/Programowanie_obiektowe):

w którym programy definiuje się za pomocą obiektów — elementów łączących stan (czyli dane, nazywane najczęściej polami) i zachowanie (czyli procedury, tu: metody). Obiektowy program komputerowy wyrażony jest jako zbiór takich obiektów, komunikujących się pomiędzy sobą w celu wykonywania zadań.

Clousures jest ideą dodatkową, która ciekawie się łączy z obiektami jak i funkcjami (w obu paradygmatach jest przydatna). Jest dopełnieniem, które zwiększa możliwości wybranego podejścia.

To zaś z czym mogę się zgodzić, to to że JS ma moc :) I że należy podchodzić do niego w odpowiedni sposób z wykorzystaniem pełnego spektrum jego cech. Problem w tym, że nigdzie nie poznałem (tak przekonywująco) pełnego opracowania wzorców do dużych projektow, które spełniają powyższe kryterium. I mam nadzieję, że właśnie Ferrante tutaj zadziała ;>

32

@Piotrek Reinmar Koszuliński

Mhm, przyznaje sie – mowiac o obiektach dokonalem razacego uproszczenia. Zalozylem mianowicie, ze najszerzej znany model obiektowy jest realizowany przez C++ – pominalem calkowicie dyskusje o innych mozliwych modelach, od Smalltalku przez Jave po Io. Przy okazji chcialem zareklamowac to: http://www.paulgraham.com/reesoo.html i to http://www.paulgraham.com/noop.html jako ciekawy wstep do rozwazan o obiektach i obiektowosci w jezykach programowania. Natomiast zaznaczam, ze te rozwazania – moim zdaniem – powinny pozostawac prywatne: bardzo trudno jest w ich trakcie uniknac tonu zarliwego wyznawcy takiego czy innego modelu.

Z drugiej strony zgadzam sie, ze obiektowosc jest podstawa JavaScriptu. Spojrzalem w podlinkowane komentarze i wyglada na to, ze temat functional-vs-oo-js juz byl poruszany i to pewnie nie raz; dalem sie na to zlapac jako kompletny nowicjusz JSowy. To samo dotyczy closures – moja droga od C++ przez OCamla do Pythona – miedzy innymi – wyksztalcila we mnie (moze przesadny) szacunek dla tej techniki programowania. Nie wiem, czy to tylko dodatek, czy moze konsekwencja obiektowego modelu, czy feature rownie wazny, co obiekty – do tego ostatniego przekonywal miedzy innymi Jamie Zawinski w “Coders at Work”. Jako poczatkujacy – i hobbystyczny – JSowiec pozostawiam to bez wlasnego komentarza :)

Jesli zas chodzi o sile wyrazu JS i ogolnie “power” jezyka, to zgadzamy sie jak widze calkowicie: JS to nie jest zabawka, a potezny, dynamiczny jezyk programowania ogolnego przeznaczenia, ktory ma pecha miec kijowe implementacje (swoja droga: istnieje jakas wzorcowa implementacja czy tylko specyfikacja ECMAScript?). Mozliwosc (oczywiscie: nalezy jej unikac) dodawania metod do obiektow np. Array poprzez ich prototype jest czyms, czego na przyklad w Pythonie w ogole nie mozna dokonac (rozumiem, ze Array jest czyms w rodzaju pythonowego list pod wzgledem dostepnosci swojej definicji: i jedno i drugie jest definiowane poza jezykiem (pewnie w C)), mozna najwyzej dziedziczyc, a i to od niedawna.

Mnie osobiscie brakuje w JS wylacznie wbudowanego mechanizmu wspierajacego modularnosc: istnieja implementacje (http://requirejs.org/), ale chyba lepiej by bylo, gdyby ‘require’ bylo slowem kluczowym jezyka, a nie zwykla funkcja definiowana przez uzytkownika.

Martwi mnie natomiast fakt, ze JS jest czesto poznawany jako pierwszy jezyk – podobnie jak PHP. To bardzo niedobrze – poczatkujacy programista nie tylko nie wykorzysta wlasciwie elastycznosci jezyka, ale z duzym prawdopodobienstwem ta sama elastycznosc zrobi mu raczej predzej niz pozniej kuku. Tutaj tez sie zgadzamy: jest bardzo duze zapotrzebowanie na tutoriale po “profesjonalnym” JS – nie kolejne “hello world”, tylko wlasnie opis wzorcow projektowych i bardziej zaawansowanych cech jezyka.

Pozdrawiam, CJI.

cji
33

@CJI: Szczerze mówiąc to nie spodziewałem się takiej zgody :) Ale może dlatego, że zazwyczaj potrafię palnąć coś głupiego, do czego można się przyczepić :D.

Jeśli chodzi o clousures, to bardzo lubię, ale w JavaScriptcie zastanowiłbym się nad ich wydajnością (nie wiem jak to się ma w innych językach). Obiło mi się o uszy i oczy, że trzeba uważać – w końcu to dodatkowy krok w dostępie do zmiennej. To znaczy dostęp do zmiennej spoza lokalnego scope’a funkcji jest wolniejszy. W innych językach jest podobnie?
Z drugiej strony – ten sam efekt uzyskujemy np. z this i dziedziczeniem prototypowym – również następuje szukanie “do góry”. Tak więc możliwe, że w wielu momentach trzeba uważać i wiele podejść do obiektowego JS-a ma wydajnościowe niespodzianki.

Jeśli chodzi o mechanizm wspierający modularność, to słyszałem też tę uwagę od innych programistów JS. Coś w tym jest, ale na pewno bardziej się to uwidacznia w większych projektach, z którymi póki co niestety nie miałem dużej styczności. BTW http://yepnopejs.com/

Martwi mnie natomiast fakt, ze JS jest czesto poznawany jako pierwszy jezyk – podobnie jak PHP. To bardzo niedobrze – poczatkujacy programista nie tylko nie wykorzysta wlasciwie elastycznosci jezyka, ale z duzym prawdopodobienstwem ta sama elastycznosc zrobi mu raczej predzej niz pozniej kuku.

Święta prawda! Elastyczność z jednej strony jest cudowna, z drugiej pozwala na zrobienie takiego szamba, że strach się bać. JS nie jest językiem dobrym dla początkującego programisty, któremu nikt nie przewodzi w jego pierwszych krokach. Widzę to w szczególności udzielając się czasami na forach…
Obawiam się jednak, że nic na to nie poradzimy. JS ma trochę opinię języka dla dzieciaków i do stronek, przez co ciężko w ogóle przyznać się, że jest się “programistą JavaScript” :)

34

Skoro już mowa o obiektach, to zaproponowałbym od siebie:
http://www.yarpo.pl/2011/01/11/tworzenie-obiektow-w-js/

Fakt, że JS i PHP są poznawane jako pierwsze języki jest szkodliwy, ale i dobry :)

Szkodliwy bo:
– wielu programistów nie rozumie, co tak naprawdę się dzieje
– “random inżyniring” na porządku dziennym [może tu coś zmienię… działa? nie.. hm, no to tu może… :P]
– słaba społeczność, patrzenie z góry przez “prawdziwych programistów”

Dobre bo:
– szeroko stosowane
– proste do nauczenia (choć JS tylko udaje, że tak jest :))

Crockford kiedyś powiedział, że “dajcie dziecku kod Javy, to nie skompiluje go, dajcie mu JavaScript a zacznie programować” [niedokładnie, ale sens taki]. Jak widać ostatnio w polskim internecie [jak i w internecie w ogóle] pojawia się bardzo dużo treści na temat JS. Dobrych i profesjonalnych.

Sądzę, że o tym, czy język [technologia] jest dobra świadczy jeden fakt: czy zarabia.

dzisiaj JS zarabia. Będzie zarabiać jeszcze więcej, zatem nie ważne, że nie było do tej pory zbyt wielu doświadczonych programistów JS. Znajdą się. Nie ważne, że nie było społeczności – na naszych oczach się tworzy.

A fakt, że łatwo zacząć z JS będzie teraz tylko działał na jej korzyść. Wejście HTML5 i [jeśli] zadomowienie się JS na urzadzeniach mobilnych da tej technologii z 10 lat el dorado.

Panowie [i Panie, jeśli obcne:)] – złote czasy JS nadchodzą :). Zacierać ręce i otwierać portfele :P

35

To ja zareklamuję:
http://www.yarpo.pl/2011/06/03/javascript-na-powaznie/

To początkowy fragment mojej magisterki. Będę wdzięczny za konstruktywną krytykę :)

36

Może nowy post z prośbą o review? ;-) Chociaż nie wiem na ile to legalne.

37

Kiedy można się spodziewać kolejnych części tego tematu ? ;)

WRB
38

tylko sobie testuje. przepraszam

fa
39

kiedy Damian będzie kolejny odcinek?

mikus
40

Witam.
Czytając Twój artykuł można odnieść wrażenie że prototype i __proto__ jest to dokładnie ta sama referencja. Już podczas pierwsze problemy. Szukałem jakiś opisów jakie są różnicę pomiędzy tymi zmiennymi, ale jakoś nie wiele z tego rozumiem.

Czy możesz dodać opis czym różnią się te pojęcia i w jakich przypadkach można ich używać ?

Dodaj komentarz

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