[home]
techniki internetowe[home] 24/IV
[top]

What kills me is the teams who get into the bad habit of holding meetings every time they need to figure out how something is going to work. Did you ever try to write poetry in a committee meeting? It’s like a bunch of fat construction guys trying to write an opera while sitting on the couch watching Baywatch. The more fat construction guys you add to the couch, the less likely you are to get opera out of it.

J. Spolsky, The Big Picture

Podstawy języka JavaScript

The Most Misunderstood Language

Według Douglasa Crockforda język JavaScript, vel Mocha, vel LiveScript, vel JScript, vel ECMAScript to: The World's Most Misunderstood Programming Language, mimo że jednocześnie jest jednym z najbardziej popularnych języków programowania. JavaScript jest tak popularny ponieważ jest językiem skryptowym WWW (interpreter języka jest zainstalowany w każdej przeglądarce).

Niestety mało kto wie, że JavaScript jest dynamicznym, obiektowym, uniwersalnym językiem programowania. Powodem tej niewiedzy jest to, że podręczniki do tego języka są przestarzałe, pełne błędów i bardzo grube.

Przegląd języka

Podstawowymi cegiełkami z których jest zbudowany język są typy. Podstawowe typy języka JavaScript to:

Na stronie A Survey of the JavaScript Programming Language znajdziemy nieco kodu, który wykonamy w powłoce Javascript.

Tablice

Użycie literału tablicowego to najprostszy sposób na utworzenie i zainicjalizowanie tablicy. Oto przykład:

var a = ["kot", "pies", "kurczak"]; a.length // 3

Metoda length niekoniecznie zwraca liczbę elementów umieszczonych w tablicy:

var a = ["kot", "pies", "kurczak"]; a[100] = "kaczka"; a.length // 101

Odwołanie się do nie istniejącego elementu tablicy zwraca undefined:

typeof(a[90])] // undefined

Uwaględniając powyższe uwagi, przechodzimy po wszystkich elementach tablicy tak:

for (var i = 0; i < a.length; i++) { // zrób coś z a[i] }

albo tak

for (var i = 0, item; item = a[i]; i++) { // zrób coś z item }

chociaż rzadko kto tak zapisuje pętlę.

Tablice mają bardzo dużo metod. Należy raz w życiu wszystkie je przejrzeć: tutaj i tutaj

Funkcje

Poniższy przykład przedstawia wszystko (no prawie wszystko) co powinniśmy wiedzieć o funkcjach:

function dodaj(x, y) { var suma = x + y; return suma; }

Funkcje mogą mieć zero lub więcej nazwanych argumentów. Treść (albo inaczej ciało) funkcji składa się z dowolnej liczby instrukcji. W treści funkcji deklarujemy jej zmienne prywatne, które są lokalne dla definiowanej funkcji. Instrukcja return zwraca wyliczoną wartość do miejsca wywołania i kończy wykonywanie funkcji. Jeśli nie zostanie wykonana instrukcja return, to funkcja zwraca wartość undefined.

Można wywołać funkcje bez przekazywania jej argumentów, których ona oczekuje. W takim wypadku przypisywana jest im wartość undefined:

dodaj() NaN // taki jest wynik dodawania undefined

Nie jest również błędem przekazanie większej niż funkcja oczekuje liczby argumentów:

dodaj(2, 4, 8) 6 // 8 zostało pominięte

W treści funkcji możemy użyć zmiennej tablicowej arguments zawierającej wszystkie wartości przekazane jej w argumentach. Poniższa funkcja dodaj sumuje wartości wszystkich jej przekazanych argumentów:

function dodaj() { var suma = 0; for (var i = 0, j = arguments.length; i < j; i++) { suma += arguments[i]; } return suma; } dodaj(2, 3, 4, 5) // 14

Oczywiście takie obliczanie sumy nie ma większego sensu. Sumę tę obliczamy pisząc po prostu 2 + 3 + 4 + 5. Ale już poniższa funkcja może być użyteczna:

function średnia() { var suma = 0; for (var i = 0, j = arguments.length; i < j; i++) { suma += arguments[i]; } return suma / arguments.length; } średnia(1, 2, 3, 4) // 2.5

Ale do obliczenia średniej z liczb umieszczonych w tablicy musimy napisać nową funkcję:

function średnia_tablicowa(arr) { var suma = 0; for (var i = 0, j = arr.length; i < j; i++) { suma += arr[i]; } return suma / arr.length; } średnia_tablicowa([1, 2, 3, 4]) // 2.5

Fajnie by było, gdyby funkcja średnia mogła obsłużyć i ten przypadek. Jest to możliwe. Wystarczy tylko umiejętnie skorzystać metody apply:

średnia.apply(null, [1, 2, 3, 4]) // 2.5

Drugi argument apply powinien zawierać tablicę, której elementy zostaną użyte jako argumenty funkcji średnia (pierwszy argument jest obiektem na którym funkcja ma być wywoływana; w treści funkcji obiekt ten zostanie przypisany do zmiennej this). Zauważmy przy okazji, że funkcje mają swoje metody. Zatem funkcje są też obiektami.

W JavaScripcie można tworzyć funkcje anonimowe (i przypisywać je do zmiennych):

var średnia = function() { var suma = 0; for (var i = 0, j = arguments.length; i < j; i++) { suma += arguments[i]; } return suma / arguments.length; }

Funkcje anonimowe umożliwiają użycie definicji funkcji w każdym miejscu w którym można użyć wyrażenia. Na przykład poniższy kod pokazuje jak można uzyskać coś w rodzaju struktury blokowej:

var a = 1; var b = 2; (function() { var b = 3; a += b; })(); a // 4 b // 2

Kto wie jaką rolę spełniają dwa ostatnie nawiasy ()?

Możemy też pisać funkcje rekurencyjne. Jest to bardzo przydatne, gdy funkcje działają na strukturach drzewiastych, na przykład na drzewie dokumentu HTML.

function policz_znaki(element) { if (element.nodeType == 3) { // TEXT_NODE return element.nodeValue.length; } var licznik = 0; for (var i = 0, dziecko; dziecko = element.childNodes[i]; i++) { licznik += policz_znaki(dziecko); } return licznik; }

Ponieważ funkcje anonimowe nie mają nazwy więc nie można ich wywoływać rekurencyjnie. Można to obejść, korzystając z metody callee tablicy arguments. Wywołanie arguments.callee zawsze odnosi się do bieżącej funkcji i dlatego możemy go użyć wywołania rekurencyjnego:

var liczba_znaków_elementu_body = (function(e) { if (e.nodeType == 3) { // TEXT_NODE return e.nodeValue.length; } var licznik = 0; for (var i = 0, dziecko; dziecko = e.childNodes[i]; i++) { licznik += arguments.callee(dizecko); } return licznik; })(document.body);

Ponieważ arguments.callee odnosi się do bieżącej funkcji i wszystkie funkcje są obiektami, więc można użyć arguments.callee to zapamiętania czegokolwiek między kolejnymi wywołaniami tej samej funkcji. Poniższa funkcja pamięta ile razy była wywoływana.

function licznik() { if (!arguments.callee.count) { arguments.callee.count = 0; } return arguments.callee.count++; } licznik() // 0 licznik() // 1 licznik() // 2

Closures (domknięcia)

D. Flanagan & Y. Matsumoto. „The Ruby Programming Language”: The term „closure” comes from the early days of computer science; it refers to an object that is both an invocable function and a variable binding for that function.

Funkcje Javascript'u mają zasięg leksykalny, a nie dynamiczny. Oznacza to, że są wykonywane w zasięgu w którym zostały zdefiniowane (leksykalny), a nie w tym, w którym są wykonywane (dynamiczny).

Inna definicja, D. Crockford:

Functions can be defined inside of other functions. The inner function has access to the vars and parameters of the outer function. If a reference to an inner function survives (for example, as a callback function), the outer function's vars also survive.

x = 0; function daj(x) { return function() { return x; } }

Teraz wykonanie poniższego kodu spowoduje wypisanie 10 i 100.

a = daj(10); alert( a() ); // wypisuje 10 b = daj(100); alert( b() ); // wypisuje 100

Zasięg obejmuje stan argumentów funkcji i zmiennych lokalnych. Chociaż argumenty funkcji i zmienne lokalne są nietrwałe, to stan ich zostaje zamrożony i staje się częścią zasięgu leksykalnego każdej z funkcji (zob. Flanagan, s. 56.)

Jeszcze jeden przykład:

function zrób_sumator(a) { return function(b) { return a + b; } } x = zrób_sumator(2); y = zrób_sumator(8); x(6) // zwraca 8 y(8) // zwraca 16

Obiekty

Użycie literału obiektowego to najprostszy sposób na utworzenie i zainicjalizowanie obiektu. Oto przykład:

var obj = { nazwa: "Myszka", "dla": "Mruczka", szczegóły: { kolor: "szara", rozmiar: 44 } }

Dostęp do atrybutów uzyskujemy w taki sposób:

obj.szczegóły.kolor // szary obj['szczegóły']['rozmiar'] // 44

Funkcje inicjalizujące obiekty nazywane są konstruktorami. Wywołanie konstruktora poprzedzamy słowem kluczowym new:

new Konstruktor(parametry...)

Zwyczajowo nazwę konstruktora zacznynamy wielką Literą. Konstruktor jest funkcją.

var Pieszczoszek = function(nazwa, wiek){ this.name = nazwa; this.age = wiek; }; // tworzymy obiekt klasy Pieszczoszek var kotek = new Pieszczoszek('Mruczek', 4); alert('Oto znany pieszczoszek ' + kotek.name);

Obiekty dziedziczą właściwości po obiekcie prototypowym. Każdy obiekt ma prototyp. Wszystkie jego właściwości stają się właściwościami obiektu, którego jest prototypem. Zatem każdy obiekt dziedziczy właściwości swojego prototypu.

Przykład klasy z obiektem prototypowym (zob. D. Flanagan. JavaScript. Przewodnik Programisty. RM 2003, s. 123–126).

Inicjalizujemy właściwości, które będą różne dla różnych obiektów tej klasy.

function Koło(x, y, r) { this.x = x; this.y = y; this.r = r; }

Definiujemy właściwość wspólną dla wszystkich Kół

Koło.prototype.pi = 3.14;

Definiujemy funkcję wspólną dla wszystkich Kół

Koło.prototype.obwód = function () { return 2* this.pi * this.r ; }

Teraz możemy utworzyć egzemplarz Koła i skorzystać z jego metod:

k = new Koło(4, 4, 1); k.obwód(); // nawiasy są konieczne; dlaczego?

Funkcję obwód można też zdefiniować w ten sposób (dopisując po this.r = r;):

this.obwód = function () { return 2* this.pi * this.r ; }

Ale nigdy tak nie postępujemy. Dlaczego?

Tworzenie nowych obiektów

Funkcja User oczekuje jednego argumentu i zapamiętuje go w bieżącym kontekście:

function User(login) { this.login = login; }

Tworzymy nowy egzemplarz funkcji. Następnie sprawdzamy czy właściwość login jest ustawiona na Anonymous:

var ja = new User("anonymous"); alert(ja.login == "anonymous"); // zwraca true

Obiekt ja jest egzemplarzem obiektu (funkcji?) User (w języku JavaScript nie ma klas):

alert(ja.constructor == User); // zwraca true

Dobre pytanie: Ponieważ User() jest po prostu funkcją, to jaki jest wynik wykonania poniższego kodu:

User("admin");

Odpowiedź: ponieważ nie został ustanowiony kontekst dla this, to został użyty kontekst domyślny obiektu window; oznacza to że właściwość window.login jest równa admin

alert( window.login == "admin" ); // zwraca true

Inne pytanie: Jaka jest różnica między

User("admin") new User("admin")

A jaka jest różnica między

ja = User("admin") ja = new User("admin")

Zrozumieć Scope & Binding

Pomoże lektura artykułu Justina Palmera: Understanding Scope and Binding in JavaScript. Poniżej przykład z tego bloga do wypróbowania w js powłoce Javascript. Przykład dotyczy funkcji apply, która umożliwia przekazanie scope/binding w pierwszym swoim argumencie.

$ js var Car = function() { this.name = 'samochód'; } var Truck = function() { this.name = 'ciężarówka'; } var func = function() { print(this.name); } var c = new Car(); var t = new Truck(); func.apply(c); func.apply(t);

Dla przypomnienia:

function apply(scope, args...) { this <-- scope wykonaj func(args...) }

I na koniec kilka przykładów z „JavaScript Closures for Dummies

John Resig (ejohn). Pro JavaScript Techniques

Kilka uwag + przykładów z rozdziału 2: Object-Oriented JavaScript: scope (zasięg), closures (domknięcia), context (kontekstu). [uruchomić aplikację Rails, repo/js/900-Ksiazki/Pro_JavaScript_Techniques]

Operatory, instrukcje... przykłady, przykłady...

Teraz kilka programów przykładowych:

I jeszcze kilka..

I jeszcze kilka..

Słowniczek: faza przechwytywania (ang. capturing phase), faza pęcherzykowa (ang. bubbling phase). Kolejność: capturing ⇨ bubbling ⇨ default. Dla zdarzeń on* nie występuje faza capturing.

I na koniec obsłużymy kilka zdarzeń:

Jesteśmy w sytuacji analogicznej do tej, w której jest ktoś, kto wie, jak poruszają się figury w szachach, ale nie wie nic o typowych otwarciach, taktyce i strategii. Tak jak początkujący szachista, nie znamy podstawowych sposobów postępowania w naszej dziedzinie.

H. Abelson, G. J. Sussman, Struktura i interpretacja programów komputerowych.

Materiały w sieci

[top]