diff --git a/.gitignore b/.gitignore index fecb262f..831db28d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /site/zh /site/en /site/fi +/site/pl /site/tr *.md~ *.src.md diff --git a/server.js b/server.js index 1ead3c14..3cc5b704 100644 --- a/server.js +++ b/server.js @@ -2,7 +2,12 @@ var build = require('./build').build, qs = require('querystring'), port = 9900, - repoURL = "https://github.com/ciembor/JavaScript-Garden"; + repoURL = "https://github.com/cramerdev/JavaScript-Garden", + options = { dir: 'doc', pathPrefix: '', template: 'garden.jade', out: 'site'}; + +// FIXME: this is done twice, once when the module loads, and once here +// (with the correct options) +build(options); require('http').createServer(function (request, response) { var payload = ''; @@ -16,7 +21,7 @@ require('http').createServer(function (request, response) { console.log(payload); payload = JSON.parse(qs.parse(payload).payload); if (payload.repository.url === repoURL) { - build(); + build(options); } else { response.writeHead(400); // Bad Request } diff --git a/site/pl/index.html b/site/pl/index.html deleted file mode 100644 index 7542ba1d..00000000 --- a/site/pl/index.html +++ /dev/null @@ -1,1911 +0,0 @@ -JavaScript Garden -

Wstęp

JavaScript Garden jest rosnącą kolekcją dokumentów o najdziwniejszych -częściach języka JavaScript. Dokumentacja pomaga uniknąć najczęściej popełnianych -błędów, sybtelnych bugów, problemów wydajnościowych oraz złych praktyk, na które -niedoświadczeni programiści JavaScript mogą natrafić próbując poznać tajniki tego -języka.

- -

JavaScript Garden nie ma na celu nauczyć Cię języka JavaScript. Podstawowa -wiedza na temat języka jest wymagana do zrozumienia zagadnień poruszanych w tym -przewodniku. Aby nauczyć się podstaw jezyka JavaScript, odwiedź znakomity -przewodnik na stronach Mozilla Developer Network.

Autorzy

Ten przewodnik jest dziełem dwóch uroczych użytkowników Stack Overflow, -Ivo Wetzel (Treść) oraz Zhang Yi Jiang (Projekt).

Kontrybutorzy

Tłumaczenie

Hosting

JavaScript Garden znajduje się na serwerach GitHub, ale dzięki wsparciu -Cramer Rozwoju posiadamy mirror na serwerze JavaScriptGarden.info.

Licencja

JavaScript Garden jest publikowany w ramach licencji MIT i kod źródłowy znajduje -się na serwerze GitHub. Jeśli znajdziesz jakieś błędy lub literówek zgłoś proszę -problem lub rozwiąż go i zgloś pull request ze swojego repozytorium. -Możesz nas także znaleźć w pokoju JavaScript na chacie Stack Overflow.

Obiekty

Wykorzystanie obiektów i ich właściwości

Wszystko w JavaScripcie zachowuje sie jak obiekt, z dwoma wyjątkami -null oraz undefined.

- -
false.toString() // 'false'
-[1, 2, 3].toString(); // '1,2,3'
-
-function Foo(){}
-Foo.bar = 1;
-Foo.bar; // 1
-
- -

Popularnym błędem jest traktowanie literałów liczbowych jak obiektu. -Spowodowane jest to specyfiką parsera JavaScript, który interpretuje kropkę -po literale liczbowym jako rozdzielenie części całkowitej od części ułamkowej -liczby.

- -
2.toString(); // wyrzuca błąd SyntaxError
-
- -

Istnieje kilka rozwiązań, dzieki którym literał liczbowy będzie zachowywał się -jak obiekt.

- -
2..toString(); // druga kropka jest poprawnie rozpoznana
-2 .toString(); // zauważ, że pozostawiona jest spacja przed kropką
-(2).toString(); // 2 zostanie najpierw zewaluowane
-
- -

Obiekty jako typy danych

- -

Obiekty w języku JavaScript mogą być używana jako tablice asocjacyjne, -ponieważ obiekty składają się głównie z mapowań pomiędzy nazwanymi właściwościami (kluczami) -a wartościami dla tych atrybutów.

- -

Używając literału obiektu - notacji {} - istnieje możliwość stworzenia obiektu prostego. -Ten nowy obiekt bedzie dziedziczył z Object.prototype oraz -nie bedzie posiadał żadnych własnych właściwości.

- -
var foo = {}; // nowy, pusty obiekt
-
-// nowy obiekt z właściwością test o wartości 12
-var bar = {test: 12}; 
-
- -

Dostęp do właściwości

- -

Właściwości obiektu można uzyskać na dwa sposoby - poprzez notację z kropką -lub z nawiasami kwadratowymi.

- -
var foo = {name: 'Kitten'}
-foo.name; // kitten
-foo['name']; // kitten
-
-var get = 'name';
-foo[get]; // kitten
-
-foo.1234; // wyrzuca błąd SyntaxError
-foo['1234']; // działa, zwraca undefined
-
- -

Obie notacje są identyczne w swoim działaniu, z tą tylko różnicą, że notacja z nawiasami -kwadratowymi pozwala na dynamiczne dodawanie właściwości i nie prowadzi do wyrzucenia -błędu podczas odczytu nieistniejącej właściwości.

- -

Usuwanie właściwości

- -

Jedynym sposobem na faktycze usunięcie własności z obiektu jest użycie operatora -delete. Ustawienie własności na undefined lub null usunie tylko wartość -związaną z własnością, ale nie usunie to klucza (nazwy własności) z obiektu.

- -
var obj = {
-    bar: 1,
-    foo: 2,
-    baz: 3
-};
-obj.bar = undefined;
-obj.foo = null;
-delete obj.baz;
-
-for(var i in obj) {
-    if (obj.hasOwnProperty(i)) {
-        console.log(i, '' + obj[i]);
-    }
-}
-
- -

Powyższy kod wypisuje dwie linie - bar undefined i foo null. Tylko własność baz -została usunięta i dlatego nie została wypisana.

- -

Notacja właściwości

- -
var test = {
-    'case': 'jestem zastrzeżonym słowem kluczowym, więc muszę być w cudzysłowie',
-    delete: 'tak samo jak ja' // wyrzuca błąd SyntaxError
-};
-
- -

Nazwy właściwości obiektu mogą być zarówno zapisane jako tekst (bez cudzysłowów -lub apostrofów) lub jako string (w cudzisłowach lub apostrofach). -Ze względu na kolejne niedociągnięcie w parserze JavaScript, -powyższy kod wyrzuci błąd SyntaxError dla implementacji JavaScript ponizej ECMAScript 5.

- -

Ten błąd wynika z faktu, że delete jest słowem kluczowym, dlatego musi zostać -zapisany jako string (z cudzysłowami lub apostrofami), aby zapewnić, że zostanie -to poprawnie zinterpretowane przez starsze silniki języka JavaScript.

Prototyp

JavaScript nie posiada klasycznego modelu dziedziczenia. Zamiast tego -dziedziczenie jest realizowane poprzez prototypy.

- -

Choć jest to często uważane za jedną ze słabości języka JavaScript, -prototypowy model dziedziczenia, jest w rzeczywistości potężniejszy od klasycznego -modelu. Na przykład stworzenia klasycznego modelu na podstawie modelu prototypowego -jest dość proste, podczas gdy zrobienie odwrotnego przekształcenie to o wiele trudniejsze zadanie.

- -

Ze względu na fakt, że w JavaScript jest w zasadzie jedynym powszechnie stosowanym -językiem, któy posiada prototypowy model dziedziczenia, dostosowanie się do różnic pomiędzy -tymi dwoma modelami wymaga trochę czasu.

- -

Pierwszą znaczącą różnicą jest to, że dziedziczenie w JavaScript odbywa się za pomocą -tak zwanych łańcuchów prototypów.

- - - -
function Foo() {
-    this.value = 42;
-}
-Foo.prototype = {
-    method: function() {}
-};
-
-function Bar() {}
-
-// Ustawienie prototypu Bar na nową instancję Foo
-Bar.prototype = new Foo();
-Bar.prototype.foo = 'Hello World';
-
-// Upewniamy się, że Bar jest ustawiony jako rzeczywisty konstruktor
-Bar.prototype.constructor = Bar;
-
-var test = new Bar() // tworzymy nową instancję Bar
-
-// The resulting prototype chain
-test [instance of Bar]
-    Bar.prototype [instance of Foo] 
-        { foo: 'Hello World' }
-        Foo.prototype
-            { method: ... }
-            Object.prototype
-                { toString: ... /* etc. */ }
-
- -

W powyższym przykładzie obiekt test będzie dziedziczył z obydwu, tj. -Bar.prototyp i Foo.prototyp, stąd będzie miał dostęp do funkcji method, -która była zdefiniowana w Foo. Ponadto obiekt będzie miał dostęp do -właściwości value, która jest jednyną instancją Foo i stała się jego prototypem. -Należy pamiętać, że new Bar nie tworzy nowej instancji Foo, -tylko wykorzystuje instancję, która jest przypisana do własności prototype. -Zatem Wszystkie instancje Bar będą dzieliły tą samą własność value.

- - - -

Wyszukiwanie własności

- -

Podczas dostępu do właściwości obiektu JavaScript przejdzie w górę łańcucha -prototypów, dopóki nie znajdzie właściwości bez nazwy.

- -

Gdy przeszukiwanie dotrze do końca (szczytu) łańcucha, mianowicie Object.prototype -i nadal nie znajdzie określonej właściwości, to zwróci wartość -undefined.

- -

Właściwość prototype

- -

Podczas gdy właściwość prototype jest używana przez język do budowania łańcucha -prototypów, istnieje możliwość przypisania do niej dowolnej wartości. Jednakże -prymitywne typy będą po prostu ignorowanie, jeżeli zostaną ustawione jako prototype.

- -
function Foo() {}
-Foo.prototype = 1; // nie ma wpływu
-
- -

Przypisywanie obiektów, jak pokazano w powyższym przykładzie, zadziała i pozwala -na dynamiczne tworzenie łańcuchów prototypów.

- -

Wydajność

- -

Czas wyszukiwania właściwości, które są na końcu łańcucha prototypów może mieć -negatywny wpływ na wydajność krytycznych części kodu. Dodatkowo, próba dostępu -do nieistniejącej właściwości zawsze spowoduje przeszukanie całego łańcucha prototypów.

- -

Również podczas iteracji po właściwościach obiektu -każda właściwość, która znajduje się w łańcuchu prototypów (niezależnie -na jakim znajduje się poziomie) zostanie wyliczona.

- -

Rozszerzanie natywnych prototypów

- -

Rozszerzanie Object.prototype lub innego prototypu wbudowanych typów jest jednym z -najczęściej nadużywanej częsci języka JavaScript.

- -

Technika ta nazywana jest monkey patching i łamie zasady enkapsulacji. -Mimo to jest szeroko rozpowszechniona w frameworkach takich jak Prototype. -Nie ma jednak dobrego powodu, aby zaśmiecać wbudowane typy poprzez wzbogacanie ich o -niestandardowe funkcjonalności.

- -

Jedynym dobrym powodem do rozszerzania wbudowanych prototypów jest portowanie
-funkcjonalności znajdujących sie w nowszych silnikach JavaScript, np. Array.forEach

- -

Wnioski

- -

Zanim przystąpi się do pisania skomplikowanego kodu korzystającego z dziedziczania,
-należy całkowicie zrozumieć prototypowy model dziedziczenia. Ponadto trzeba uważać -na długość łańcucha prototypów i w razie potrzeby zmniejszać ilość dziedziczeń, -aby uniknąć problemów z wydajnością. Natywne prototypy nigdy nie powinny być -rozszerzane, chyba że ze względu na wprowadzanie kompatybilności z nowszymi silnikami -JavaScript.

hasOwnProperty

W celu sprawdzenia, czy dana właściwość została zdefiniowana w tym obiekcie, a nie -w łańcuchu prototypów, niezbędne jest skorzystanie z metody -hasOwnProperty, której wszystkie obiekty dziedziczą z Object.prototype.

- - - -

hasOwnProperty jest jedyną metodą w języku JavaScript, która operuje na właściwościach -i nie przegląda całego łańcucha prototypów.

- -
// Zatrucie Object.prototype
-Object.prototype.bar = 1; 
-var foo = {goo: undefined};
-
-foo.bar; // 1
-'bar' in foo; // true
-
-foo.hasOwnProperty('bar'); // false
-foo.hasOwnProperty('goo'); // true
-
- -

Tylko hasOwnProperty da prawidłowy i oczekiwany rezultat. Jest to istotne podczas -iteracji po właściwościach obiektu. Nie ma innego sposobu na ominięcie -właściwości, która nie została zdefiniowana przez ten konkretny obiekt, -ale gdzieś indziej w łańcuchu prototypów.

- -

hasOwnProperty jako właściwość

- -

JavaScript nie chroni właściwości o nazwie hasOwnProperty, zatem istnieje -możliwość, że obiekt będzie posiadać tak nazwaną właściwość. Konieczne jest użycie -zewnętrznego hasOwnProperty, aby otrzymać poprawne rezultaty.

- -
var foo = {
-    hasOwnProperty: function() {
-        return false;
-    },
-    bar: 'Here be dragons'
-};
-
-foo.hasOwnProperty('bar'); // zawsze zwraca false
-
-// Została użyta metoda innego obiektu i wywołana z konkekstem 
-// `this` ustawionym na foo
-({}).hasOwnProperty.call(foo, 'bar'); // true
-
- -

Wnioski

- -

Jedyną metodą służącą do sprawdzenia istnienia jakiejś właściwości w konkretnym -obiekcie jest metoda hasOwnProperty. Zaleca się korzystać z hasOwnProperty w -każdej pętli for in. Pozwoli to uniknąć błędów pochodzących -z rozszerzonych natywnych prototypów.

Pętla for in

Podobnie jak operator in, pętla for in przeszukuje łańcuch prototypów -podczas iteracji po właściwościach obiektu.

- - - -
// Zatrucie Object.prototype
-Object.prototype.bar = 1;
-
-var foo = {moo: 2};
-for(var i in foo) {
-    console.log(i); // wyświetla obie właściwości: bar i moo
-}
-
- -

Ponieważ zmiana zachowania pętli for in nie jest możliwa, niezbędne -jest odfiltrowanie niechcianych właściwości wewnątrz ciała pętli, korzystając -z metody hasOwnProperty z Object.prototype.

- - - -

Filtrowania przy użyciu hasOwnProperty

- -
// foo z przykładu powyżej
-for(var i in foo) {
-    if (foo.hasOwnProperty(i)) {
-        console.log(i);
-    }
-}
-
- -

To jest jedyna poprawna wersja, której należy używać. Ze względu na użycie -hasOwnProperty zostanie wypisane jedynie moo. Gdy opuścimy hasOwnProperty, -kod będzie podatny na błędy, gdy natywne prototypy (np. Object.prototype) -zostaną rozszerzone.

- -

Prototype jest jednym z popularniejszych frameworków, które dokonują -takiego rozszerzenia. Używanie tego frameworku oraz nie stosowanie w pętli for in -metody hasOwnProperty gwarantuje błędy w wykonaniu.

- -

Wnioski

- -

Zaleca się, aby zawsze używać metody hasOwnProperty. Nigdy nie powinno się dokonywać -żadnych założeń na temat środowiska, w którym kod będzie wykonywany ani tego, czy -natywne prototypy zostały rozszerzone, czy nie.

Funkcje

Deklaracje funkcji i wyrażenia funkcyjne

Funcje w języku JavaScript są typami pierwszoklasowymi, co oznacza, że mogą -być przekazywane jak każda inna wartość. Jednym z typowych zastosowań tej cechy -jest przekazywanie anonimowej funkcji jako callback do innej, prawdopodobnie -asynchronicznej funkcji.

- -

Deklaracja funckcji

- -
function foo() {}
-
- -

Powyższa funkcja zostaje wyniesiona zanim program wystartuje. Dzięki temu -jest dostępna wszędzie w ramach zasięgu, w którym została zadeklarowana, -nawet, jeżeli ta funkcja została wywołana przed faktyczną definicją w kodzie źródłowym.

- -
foo(); // Działa ponieważ definicja funkcji została wyniesiona 
-       // na początek zasięgu przed uruchomieniem kodu
-function foo() {}
-
- -

Wyrażenie funkcyjne

- -
var foo = function() {};
-
- -

Ten przykład przypisuje nienazwaną i anonimową funkcję do zmiennej foo.

- -
foo; // 'undefined'
-foo(); // wyrzuca błąd TypeError
-var foo = function() {};
-
- -

Ze względu na fakt, że deklaracja var wynosi zmienną foo na początek zasięgu -zanim kod faktycznie zostanie uruchomiony, foo będzie zdefiniowane kiedy skrypt -będzie wykonywany.

- -

Ale ponieważ przypisania robione są dopiero podczas wykonania, wartość foo będzie -ustawiona na domyślną wartość undefined zanim powyższy kod -zostanie uruchomiony.

- -

Nazwane wyrażenia funkcyjne

- -

Kolejnym specjalnym przypadkiem jest przypisanie nazwanej funkcji.

- -
var foo = function bar() {
-    bar(); // Działa
-}
-bar(); // wyrzuca ReferenceError
-
- -

W zewnętrznym zakresie bar nie będzie dostępna, ponieważ funkcja zostaje -przypisana do foo, jednakże w wewnętrznym zakresie bar będzie dostępna. -Jest to spowodowane tym, jak działa rozwiązywanie nazw -w języku JavaScript. Nazwa funkcji jest zawsze dostępna w lokalnym -zakresie tej funkcji.

Jak działa this

JavaScript posiada inną koncepcję odnośnie tego na co wskazuje słowo kluczowe -this, niż większość innych języków programowania. Istnieje dokładnie -pięć różnych sytuacji, w których wartość this jest przypisana w języku JavaScript.

- -

JavaScript has a different concept of what the special name this refers to -than most other programming languages do. There are exactly five different -ways in which the value of this can be bound in the language.

- -

Zasięg globalny

- -
this;
-
- -

Używanie this w globalnym zasięgu, zwróci po prostu referencję do obiektu global.

- -

Wywołanie funkcji

- -
foo();
-
- -

Tutaj this również będzie wkazywało na obiekt global

- - - -

Wywoływanie metody

- -
test.foo(); 
-
- -

W tym przypadku this będzie wskazywało na test.

- -

Wywołanie konstruktora

- -
new foo(); 
-
- -

Wywołanie funkcji, które jest poprzedzone słowem kluczowym new, zachowuje się -jak konstruktor. Wewnątrz funkcji this będzie -wskazywało na nowo utworzony obiekt.

- -

Jawne ustawienie this

- -
function foo(a, b, c) {}
-
-var bar = {};
-foo.apply(bar, [1, 2, 3]); // tablica zostanie zamieniona w to co poniżej
-foo.call(bar, 1, 2, 3); // rezultat a = 1, b = 2, c = 3
-
- -

Używając metod call lub apply z prototypu Function.prototype, wartość this -wewnątrz wołanej funkcji zostanie jawnie ustawiona na pierwszy argument przekazany -podczas wywołania tych metod.

- -

Zatem w powyższym przykładzie przypadek Wywoływanie metody nie będzie miał -miejsca i this wewnątrz foo będzie wskazywać na bar.

- - - -

Częste pułapki

- -

Mimo iż Większość z tych przypadków ma sens, to pierwszy przypadek powinien być -traktorany jako błąd podczas projektowania języka i nigdy nie wykorzystywany -w praktyce.

- -
Foo.method = function() {
-    function test() {
-        // wewnątrz tej funkcji this wskazuje na obiekt global
-    }
-    test();
-}
-
- -

Powszechnym błędem jest myślenie, że this wewnątrz test wskazuje na Foo, -podczas gdy w rzeczywistości tak nie jest.

- -

Aby uzyskać dostęp do Foo wewnątrz test, niezbędne jest stworzenie wewnątrz -metody lokalnej zmiennej, która będzie wskazywała na Foo.

- -
Foo.method = function() {
-    var that = this;
-    function test() {
-        // Należy używać that zamiast this wewnątrz tej funkcji
-    }
-    test();
-}
-
- -

that jest zwykłą zmienną, ale jest to powszechnie stosowana konwencja otrzymywania
-wartości zewnętrznego this. W połączeniu z domknięciami(closures), -jest to sposób na przekazywanie wartości this wokół.

- -

Metody przypisywania

- -

Kolejną rzeczą, która nie działa w języku JavaScript, jest nadawanie aliasów -funkcjom, co oznacza przypisanie metody do zmiennej.

- -
var test = someObject.methodTest;
-test();
-
- -

Podobnie jak w pierwszym przypadku test zachowuje się jak wywołanie zwykłej -funkcji, a zatem wewnątrz funkcji this już nie będzie wskazywało someObject.

- -

Podczas gdy późne wiązanie this może się na początku wydawać złym pomysłem, -to w rzeczywistości jest to rzecz, która sprawia, że -dziedziczenie prototypowe działa.

- -
function Foo() {}
-Foo.prototype.method = function() {};
-
-function Bar() {}
-Bar.prototype = Foo.prototype;
-
-new Bar().method();
-
- -

Kiedy metoda method zostanie wywołana na instancji Bar, this będzie -wskazywało właśnie tę instancję.

Domknięcia i referencje

Jedną z najpotężniejszych funkcjonalności języka JavaScript są domknięcia. -Oznacza to że zasięg zawsze posiada dostęp do zewnętrznego zasięgu, w którym -został zdefiniowany. Ponieważ zasięg w JavaScript można definiować tylko poprzez -funckję, wszystkie funkcje domyślnie zachowują się jak domknięcia.

- -

Emulowanie prywatnych zmiennych

- -
function Counter(start) {
-    var count = start;
-    return {
-        increment: function() {
-            count++;
-        },
-
-        get: function() {
-            return count;
-        }
-    }
-}
-
-var foo = Counter(4);
-foo.increment();
-foo.get(); // 5
-
- -

Tutaj Counter zwraca dwa domknięcia: funkcję increment oraz funkcję get. -Obie te funkcje trzymają referencję do zasięgu Counter, a co za tym idzie -zawsze posiadają dostęp do zmiennej count tak, jakby ta zmienna była zdefiniowana -w zasięgu tych funkcji.

- -

Dlaczego zmienne prywatne działają?

- -

Ponieważ nie ma możliwości wskazania lub przypisania zasięgu w JavaScript, -nie istnieje sposób, aby uzyskać dostęp do zmiennej count z zewnątrz. -Wykorzystanie tych dwóch domknięć jest jedynym sposobem na interakcję z tą zmienną.

- -
var foo = new Counter(4);
-foo.hack = function() {
-    count = 1337;
-};
-
- -

Powyższy kod nie zmieni wartości zmiennej count wewnątrz zasięgu Counter, -ponieważ foo.hack nie została zadeklarowana wewnątrz tego konkretnego zasięgu. -Zamiast tego funkcja utworzy lub nadpisze globalną zmienną count.

- -

Domknięcia wewnątrz pętli

- -

Jednym z częstrzych błędów jest wykorzystywanie domknięć wewnątrz pętli, -aby wartość zmiennej po której odbywa się iteracja była kopiowana do -wewnętrznej funkcji.

- -
for(var i = 0; i < 10; i++) {
-    setTimeout(function() {
-        console.log(i);  
-    }, 1000);
-}
-
- -

Powyższy kod nie wypisze numerów od 0 do 9, ale wypisze -dziesięć razy liczbę 10.

- -

Anonimowa funkcja trzyma wskaźnik do zmiennej i i podczas uruchomienia -console.log, pętla for już zakończyła działanie i wartość zmiennej i -została ustawiona na 10.

- -

Aby otrzymać zamierzony efekt, niezbędne jest skopiowanie wartości -zmiennej i.

- -

Unikanie problemu z referencją

- -

Aby skopiować wartość zmiennej, po której iterujemy w pętli, należy skorzystać -z anonimowego wrappera.

- -
for(var i = 0; i < 10; i++) {
-    (function(e) {
-        setTimeout(function() {
-            console.log(e);  
-        }, 1000);
-    })(i);
-}
-
- -

Zewnętrzna anonimowa funkcja zostanie wywołana od razu z parametrem i -jako pierwszym argumentem oraz otrzyma kopię wartości zmiennej i jako -zmienną e.

- -

Anonimowa funkcja która zostaje przekazana do setTimeout teraz posiada -referencję do zmiennej e, która nie zostanie zmieniona przez pętle for.

- -

Istnieje jeszcze jeden sposób na osiągnięcie tego samego efektu. Należy zwrócic -fukcję z anonimowego wrappera, wówczas kod będzie zachowywał się jak ten -wcześniejszy.

- -
for(var i = 0; i < 10; i++) {
-    setTimeout((function(e) {
-        return function() {
-            console.log(e);
-        }
-    })(i), 1000)
-}
-

Obiekt arguments

Każdy zasięg funkcyjny w języku JavaScript ma dostęp do specjalnej zmiennej arguments. -Ta zmienna trzyma listę wszystkich argumentów przekazanych do funkcji.

- - - -

Obiekt arguments nie jest typu Array. Mimo że posiada pewne cechy -semantyki tablic - właściwość length - to w rzeczywistości nie dziedziczy -on z Array.prototype, tylko z Object.

- -

Ze względu na to, na obiekcie arguments nie można używać standardowych dla tablic metod, -takich jak push, pop czy slice. Mimo że iteracja przy pomocy -pętli for działa dobrze, to aby skorzystać ze standardowych metod tablicowych -należy skonwertować arguments do prawdziwego obiekt Array.

- -

Konwersja do tablicy

- -

Poniższy kod zwróci nowy obiekt Array zawierający wszystkie elementy -obiektu arguments.

- -
Array.prototype.slice.call(arguments);
-
- -

Jednakże konwersja ta jest wolna i nie jest zalecana w sekcjach, -które mają duży wpływ na wydajność.

- -

Przekazywanie argumentów

- -

Zalecany sposób przekazywania argumentów z jednej funkcji do następnej -wyglada następująco:

- -
function foo() {
-    bar.apply(null, arguments);
-}
-function bar(a, b, c) {
-    // do stuff here
-}
-
- -

Kolejną sztuczką jest użycie razem call i apply w celu stworzenia -szybkich i nieograniczonych wrapperów.

- -
function Foo() {}
-
-Foo.prototype.method = function(a, b, c) {
-    console.log(this, a, b, c);
-};
-
-// Stworzenie nieograniczoną wersję metody "method" 
-// która przyjmuje parametry: this, arg1, arg2...argN
-Foo.method = function() {
-
-    // Rezultat: Foo.prototype.method.call(this, arg1, arg2... argN)
-    Function.call.apply(Foo.prototype.method, arguments);
-};
-
- -

Parametry formalne i indeksy argumentów

- -

Obiekt arguments tworzy funkcje getter i setter nie tylko dla swoich -właściwości, ale również dla parametrów formalnych funkcji.

- -

W rezultacie zmiana wartości parametru formalnego zmieni również wartość -odpowiadającemu mu wpisowi w obiekcie arguments. Zachodzi to również w drugą stronę.

- -
function foo(a, b, c) {
-    arguments[0] = 2;
-    a; // 2                                                           
-
-    b = 4;
-    arguments[1]; // 4
-
-    var d = c;
-    d = 9;
-    c; // 3
-}
-foo(1, 2, 3);
-
- -

Mity i prawdy o wydajności

- -

Obiekt arguments jest tworzony zawsze, z wyjątkiem dwóch przypadków, gdy -zmienna o takiej nazwie jest zdefiniowana wewnątrz funkcji lub jeden z parametrów -formalnych funkcji ma taką nazwę. Nie ma znaczenia czy obiekt arguments jest -używany czy nie.

- -

Zarówno gettery jak i settery są zawsze tworzone, zatem używanie ich nie ma -praktycznie żadnego wpływu na wydajność. Zwłaszcza w rzeczywistym kodzie, który -wykorzystuje coś więcej niż tylko prosty dostęp do właściwości obiektu arguments.

- - - -

Jednakże, istnieje jeden przypadek w którym wydajność drastycznie spada w -nowoczesnych silnikach JavaScript. Ten przypadek to wykorzystanie -arguments.callee.

- -
function foo() {
-    arguments.callee; // operowanie na obiekcie funkcji
-    arguments.callee.caller; // i obiekcie funkcji wywołującej
-}
-
-function bigLoop() {
-    for(var i = 0; i < 100000; i++) {
-        foo(); // Normalnie zostałaby wykorzystana metoda inline
-    }
-}
-
- -

W powyższym przykładzie foo nie może zostać wykorzystana metoda inline -ponieważ potrzebne są nie tylko informacje na własny temat ale również -na temat funkcji wywołującej. Takie użycie nie tylko uniemożliwia -inlining i korzyści z niego wynikające, ale też łamie zasady enkapsulacji, -ponieważ ta funkcja jest zależna od kontekstu w jakim została wywołana.

- -

Mocno zalecane jest aby nigdy nie korzystać z arguments.callee -i żadnej jej własności.

- -

Konstruktory

Konstruktory w JavaScript również wyglądają inaczej niż innych językach. Każde -wywołanie funkcji, które jest poprzedone słowem kluczowym new, zachowuje się -jak konstruktor.

- -

Wewnątrz konstruktora - wywoływanej fukcji - wartość this wskazuje na -nowo utworzony obiekt Object. Prototyp prototype tego -nowego obiektu będzie wskazywał na prototyp prototype obiektu fukcji, -która została wywołana jako konstruktor.

- -

Jeżeli wywołana funkcja nie posiada jawnej deklaracji return, wówczas -fukcja domyślnie zwraca wartość this - nowy obiekt.

- -
function Foo() {
-    this.bla = 1;
-}
-
-Foo.prototype.test = function() {
-    console.log(this.bla);
-};
-
-var test = new Foo();
-
- -

Powyżej wywołanya została funkcja Foo jako konstruktor oraz ustawia -nowo utworzonemu obiektowi właściwość prototype na Foo.prototype.

- -

W tym przypadku jawna deklaracja return w funkcji zwraca wartość -ustawioną w deklaracji, ale tylko jeżeli zwracaną wartością jest -obiekt Object.

- -
function Bar() {
-    return 2;
-}
-new Bar(); // nowy obiekt
-
-function Test() {
-    this.value = 2;
-
-    return {
-        foo: 1
-    };
-}
-new Test(); // zwrócony obiekt
-
- -

Jeżeli słowo kluczowe new zostanie pominięte, funkcja nie zwróci nowego -obiektu.

- -
function Foo() {
-    this.bla = 1; // zostanie ustawiona w obiekcie global
-}
-Foo(); // undefined
-
- -

Mimo że powyższy kod może zadziałać w pewnych przypadkach, w związku -z działaniem this w języku JavaScript, to jako -wartość thiszostanie wykorzystany obiekt global.

- -

Fabryki

- -

Aby móc ominąć słowo kluczowe new, konstruktor musi jawnie zwracać wartość.

- -
function Bar() {
-    var value = 1;
-    return {
-        method: function() {
-            return value;
-        }
-    }
-}
-Bar.prototype = {
-    foo: function() {}
-};
-
-new Bar();
-Bar();
-
- -

Oba wywołania Bar zwrócą tę samą rzecz, nowo utworzony obiekt, który posiada -właściwość nazwaną method i dla którego Bar jest Domknięciem.

- -

Należy również pamiętać, że wywołanie new Bar() nie ma wpływu na -prototyp zwróconego obiektu (prototypem będzie object.prototype a nie Bar.prototype). -Kiedy prototyp zostanie przypisany do nowo utworzonego obiektu, Bar nidgy -nie zwróci tego nowego obiektu Bar, tylko literał obiektu, który jest po -słowie kluczowym return.

- -

W powyższym przykładzie nie ma żadnej różnicy w działaniu pomiędzy użyciem -i nieużyciem słowa kluczowego new.

- -

Tworzenie nowych obiektów korzystając z fabryk

- -

Często zaleca się nie korzystać z operatora new, ponieważ zapominanie -o jego stosowaniu może prowadzić do błędów.

- -

W celu stworzenia nowego obiektu, powinno się używać fabryki i konstruować -nowy obiekt wewnątrz tej fabryki.

- -
function Foo() {
-    var obj = {};
-    obj.value = 'blub';
-
-    var private = 2;
-    obj.someMethod = function(value) {
-        this.value = value;
-    }
-
-    obj.getPrivate = function() {
-        return private;
-    }
-    return obj;
-}
-
- -

Mimo że powyższy kod jest odporny na brak słowa kluczowego new i ułatwia -korzystanie ze zmiennych prywatnych, to posiada -pewne wady. -While the above is robust against a missing new keyword and certainly makes -the use of private variables easier, it comes with some -downsides. - 1. Zużywa więcej pamięci, ponieważ tworzony obiekt nie współdzieli metod - poprzez prototyp. - 2. Aby móc dziedziczyć fabryka musi skopiować wszystkie metody z dziedziczonego - obiektu lub przypisać ten obiekt, z którego się dziedziczy, jako prototyp - do nowo utworzonego obiektu. - 3. Porzucenie łańcucha prototypów tylko ze względu na opuszczone słowo kluczowe - new jest sprzeczne z duchem języka.

- -

Wnioski

- -

Pominięcie słowa kluczowego new może prowadzić do błędów, ale na pewno nie -powinno to być powodem odrzucenia używania prototypów w ogóle. Sprowadza się to -do wyboru rozwiązania, które bardziej pasuje do potrzeb aplikacji. Szczególnie -ważne jest, aby wybrać określony styl tworzenia obiektów i trzymać się go.

Zasięg zmiennych i przestrzenie nazw

Mimo że JavaScript radzi sobie dobrze ze składnią opisującą dwa pasujące -nawiasy klamrowe jako blok, to jednak nie wspiera zasięgu blokowego. -Jedynym zasięgiem jaki istnieje w JavaScript jest zasięg funkcyjny.

- -
function test() { // definiuje zasięg (scope)
-    for(var i = 0; i < 10; i++) { // nie definiuje zasięgu (scope)
-        // count
-    }
-    console.log(i); // 10
-}
-
- - - -

W JavaScripcie nie ma również przestrzeni nazw, co oznacza, że wszystko jest -definiowane w jednej globalnie współdzielonej przestrzeni nazw.

- -

Z każdym odwołaniem do zmiennej, JavaScript przeszukuje w górę wszystkie zasięgi -dopóki nie znajdzie tej zmiennej. W przypadku, gdy przeszukiwanie dotrze do globalnego -zasięgu i nadal nie znajdzie żądanej nazwy, to wyrzuca błąd ReferenceError.

- -

Zmora globalnych zmiennych

- -
// script A
-foo = '42';
-
-// script B
-var foo = '42'
-
- -

Powyższe dwa skrypty nie dają tego samego efektu. Skrypt A definiuje zmienną -nazwaną foo w globalnym zasięgu, natomiast skrypt B definiuje foo -w aktualnym zasięgu.

- -

Jeszcze raz, to wcale nie daje tego samego efektu. Nie użycie var może mieć -poważne konsekwencje.

- -
// globalny zasięg
-var foo = 42;
-function test() {
-    // lokalny zasięg
-    foo = 21;
-}
-test();
-foo; // 21
-
- -

Pominięcie słowa var w deklaracji wewnątrz funkcji test nadpisze wartość -zmiennej globalnej foo. Mimo że nie wygląda to na początku na duży problem, -posiadanie wielu tysięcy linii kodu w JavaScript i nie korzystanie z var -wprowadzi straszne i trudne do wyśledzenia błędy.

- -
// globalny zasięg 
-var items = [/* jakaś lista */];
-for(var i = 0; i < 10; i++) {
-    subLoop();
-}
-
-function subLoop() {
-    // scope of subLoop
-    for(i = 0; i < 10; i++) { // brakuje słowa var w deklaracji
-        // do amazing stuff!
-    }
-}
-
- -

Zewnętrzna pętla zakończy działanie po pierwszym wywołaniu subLoop, ponieważ -subLoop nadpisuje wartość globalnej zmiennej i. Użycie var w drugiej pętli -for pozwoliłoby łatwo uniknąć problemu. Słowo kluczowe var nie powinno być -nigdy pominięte w deklaracji, chyba że pożądanym skutkiem jest wpłynięcie na -zewnętrzny zasięg.

- -

Lokalne zmienne

- -

Jedynym źródłem zmiennych lokalnych w JavaScripcie są parametry funkcji -oraz zmienne zadeklarowane poprzez deklaracje var wewnątrz funkcji.

- -
// globalny zasięg
-var foo = 1;
-var bar = 2;
-var i = 2;
-
-function test(i) {
-    // lokalny zasięg fukcji test
-    i = 5;
-
-    var foo = 3;
-    bar = 4;
-}
-test(10);
-
- -

Zmienne foo oraz i są lokalnymi zmiennymi wewnątrz zasiegu funkcji test, -natomiast przypisanie wartości do bar nadpisze zmienną globalną o tej samej nazwie.

- -

"Hoisting" - wywindowanie, podnoszenie

- -

JavaScript winduje deklaracje. Oznacza to, że zarówno deklaracja ze słowem -kluczowym var jak i deklaracje funkcji function zostaną przeniesione na -początek otaczającego zasięgu.

- -
bar();
-var bar = function() {};
-var someValue = 42;
-
-test();
-function test(data) {
-    if (false) {
-        goo = 1;
-
-    } else {
-        var goo = 2;
-    }
-    for(var i = 0; i < 100; i++) {
-        var e = data[i];
-    }
-}
-
- -

Powyższy kod zostanie przekształcony przed rozpoczęciem wykonania. JavaScript -przeniesie deklarację zmiennej var oraz deklarację funkcji function na szczyt -najbliższego zasięgu.

- -
// deklaracje var zostaną przeniesione tutaj
-var bar, someValue; // ustawione domyślnie na 'undefined'
-
-// deklaracje funkcji zostaną również przeniesione na górę
-function test(data) {
-    var goo, i, e; // brak blokowego zasięgu spowoduje przeniesienie tutaj
-    if (false) {
-        goo = 1;
-
-    } else {
-        goo = 2;
-    }
-    for(i = 0; i < 100; i++) {
-        e = data[i];
-    }
-}
-
-bar(); // powoduje błąd TypeError ponieważ bar jest nadal 'undefined'
-someValue = 42; // przypisania nie zostają zmienione przez 'hoisting'
-bar = function() {};
-
-test();
-
- -

Brak blokowego zasięgu nie tylko przeniesie deklaracje var poza ciało pętli, -ale również spowoduje, że niektóre porównania if staną się nieintuicyjne.

- -

W oryginalnym kodzie instrukcja warunkowa if zdaje się modyfikować zmienną -globalną goo, podczas gdy faktycznie modyfikuje ona zmienną lokalną - po tym -jak zostało zastosowane windowanie (hoisting).

- -

Bez wiedzy na temat podnoszenia (hoistingu), poniższy kod może sprawiać wrażenie, -że zobaczymy błąd ReferenceError.

- -
// sprawdz czy SomeImportantThing zostało zainicjalizowane
-if (!SomeImportantThing) {
-    var SomeImportantThing = {};
-}
-
- -

Oczywiście powyższy kod działa ze względu na fakt, że deklaracja var zostanie -przeniesiona na początek globalnego zasięgu.

- -
var SomeImportantThing;
-
-// inny kod który może ale nie musi zainicjalizować SomeImportantThing
-
-// upewnienie sie, że SomeImportantThing zostało zainicjalizowane
-if (!SomeImportantThing) {
-    SomeImportantThing = {};
-}
-
- -

Kolejność rozwiązywania nazw

- -

Wszystkie zasięgi w JavaScripcie, włączając globalny zasięg, posiadają -zdefiniowaną wewnątrz specjalną nazwę this, która wskazuje -na aktualny obiekt.

- -

Zasięg funkcyjny posiada również zdefiniowaną wewnętrznie nazwę -arguments, która zawiera listę argumentów przekazaną do -funkcji.

- -

Na przykład, kiedy próbujemy odczytać zmienną foo wewnątrz zasięgu funkcji, -JavaScript będzie szukać nazwy w określonej kolejności: - 1. Jeżeli wewnątrz aktualnego zasięgu znajduje się deklaracja var foo skorzystaj z niej. - 2. Jeżeli jeden z parametrów fukcji został nazwany foo użyj go. - 3. Jeżeli fukcja została nazwana foo skorzystaj z tego. - 4. Przejdz do zewnętrznego zasięgu i przejdz do kroku #1.

- - - -

Przestrzenie nazw

- -

Powszechnym problemem posiadania tylko jednej globalnej przestrzeni nazw jest -prawdopodobieństwo wystąpienia kolizji nazw. W JavaScripcie, można łatwo uniknąć -tego problemu korzystając z anonimowych wrapperów.

- -
(function() {
-    // autonomiczna "przestrzeń nazw"
-
-    window.foo = function() {
-        // wyeksponowane domkniecie (closure)
-    };
-
-})(); // natychmiastowe wykonanie funkcji
-
- -

Anonimowe funkcje są rozpoznane jako wyrażenia, więc -aby mogły zostać wywołane muszą zostać zewaluowane.

- -
( // zewaluowanie funkcji znajdującej się wewnątrz nawiasów
-function() {}
-) // zwrócenie obiektu funkcji
-() // wywołanie rezultatu ewaluacji
-
- -

Istnieją inne sposoby aby zewaluować i wykonać wyrażenie funkcyjne. Mimo że -mają inną składnię, zachowują się dokładnie tak samo.

- -
// Trzy inne sposoby
-!function(){}();
-+function(){}();
-(function(){}());
-
- -

Wnioski

- -

Zaleca się, aby zawsze używać anonimowych wrapperów do hermetyzacji kodu wewnątrz -jego własnej przestrzeni nazw. To nie tylko chroni kod przed kolizją nazw, ale -również wprowadza lepszą modularyzację programów.

- -

Ponadto, stosowanie zmiennych globalnych jest uznawane za złą praktykę. -Wykorzystanie zmiennych globalnych wskazuje na źle napisany kod, który -jest podatny na błędy i trudny do utrzymania.

Tablice

Iterowanie po tablicach oraz właściwościach tablic

Mimo że tablice w JavaScript są obiektami, nie ma dobrych powodów aby używać -pętli for in do iteracji po nich. W rzeczywstości istnieje -wiele dobrych powodów przeciwko wykorzystaniu for in na tablicach.

- - - -

Ponieważ pętla for in wylicza wszystkie właściwości, które są wewnątrz -łańcucha prototypów i jedynym sposobem aby wykluczyć te właściwości jest użycie -hasOwnProperty, ale wówczas pętla staje się -dwadzieście razy wolniejsza od normalnej pętli for.

- -

Iteracja

- -

W celu osiągnięcia najlepszej wydajności podczas iteracji po tablicach należy -użyć klasycznej pętli for.

- -
var list = [1, 2, 3, 4, 5, ...... 100000000];
-for(var i = 0, l = list.length; i < l; i++) {
-    console.log(list[i]);
-}
-
- -

W powyższym przykładzie jest jeszcze jeden dodatkowy haczyk. Jest to zbuforowanie -długości tablicy poprzez l = list.length.

- -

Mimo że właściwość length jest zdefiniowana wewnątrz tablicy, istnieje nadal -dodatkowy koszt na wyszukiwanie tej właściwości przy każdej iteracji w pętli. -Chociaż najnowsze silniki JavaScript mogą zastosować w tym -przypadku optymalizację. Nie ma jednak możliwość ustalenia czy kod będzie wykonywany w jednym -z tych nowych silników, czy też nie.

- -

W rzeczywistości pominięcie buforowania długości tablicy może spowodować, że pętla -będzie tylko w połowie tak szybka jak ta z buforowaniem długości.

- -

Właściwość length

- -

Mimo, że getter właściwości length zwraca po prostu liczbę elementów, które są -zawarte w tablicy, to setter może być użyty do skracania tablicy.

- -
var foo = [1, 2, 3, 4, 5, 6];
-foo.length = 3;
-foo; // [1, 2, 3]
-
-foo.length = 6;
-foo; // [1, 2, 3]
-
- -

Przypisanie mniejszej długości spowoduje skrócenie tablicy, ale zwiększenie wartości -length nie ma żadnego wpływu na tablicę.

- -

Wnioski

- -

Aby uzyskać najlepszą wydajność zaleca się, aby zawsze używać zwykłej pętli for -i zbuforowanie właściwości length. Korzystanie z pętli for in na tablicy jest -oznaką źle napisanego kodu, który jest podatny na błędy i ma słabą wydajność.

Konstruktor Array

Zaleca się zawsze korzystać z literału tablicy - notacja [] - podczas tworzenia -nowych tablic, ponieważ konstruktor Array niejednoznacznie interpretuje -przekazane do niego parametry.

- -
[1, 2, 3]; // Rezultat: [1, 2, 3]
-new Array(1, 2, 3); // Rezultat: [1, 2, 3]
-
-[3]; // Rezultat: [3]
-new Array(3); // Rezultat: []
-new Array('3') // Rezultat: ['3']
-
- -

W przypadku gdy tylko jeden argument zostanie przekazany do kostruktora Array i -ten argument jest typu Number, konstruktor zwróci nową dziwną tablicę -z ustawioną właściwością length na wartość przekazaną jako argument. Należy -zauważyć, że tylko właściwość length zostanie ustawiona w ten sposób. -Rzeczywiste indeksy w tej tablicy nie zostaną zainicjalizowane.

- -
var arr = new Array(3);
-arr[1]; // undefined
-1 in arr; // zwraca false, indeks nie został ustawiony
-
- -

Możliwość ustalenia z góry długości tablicy jest użyteczna tylko w kilku -przypadkach, jak np. powtarzanie ciągu znaków, w którym unika się stosowania -pętli for.

- -
// count - ilosc powtorzen
-// stringToRepeat - ciąg znaków do powtórzenia 
-new Array(count + 1).join(stringToRepeat); 
-
- -

Wnioski

- -

W miarę możliwości należy unikać używania konstruktora Array. Literały są -zdecydowanie lepszym rozwiązaniem. Są krótsze i mają bardziej precyzyjną składnię. -Zwiększają również czytelność kodu.

Typy

Równość i porównania

JavaScript posiada dwa różne sposoby równościowego porównywania obiektów.

- -

Operator równości

- -

Operator równości składa się z dwóch znaków "równa się": ==

- -

JavaScript jest słabo typowanym językiem. Oznacza to, że operator równości -konwertuje typy (dokonuje koercji), aby wykonać porównanie.

- -
""           ==   "0"           // false
-0            ==   ""            // true
-0            ==   "0"           // true
-false        ==   "false"       // false
-false        ==   "0"           // true
-false        ==   undefined     // false
-false        ==   null          // false
-null         ==   undefined     // true
-" \t\r\n"    ==   0             // true
-
- -

Powyższa tabela przedstawia wyniki koercji typów. Nieprzewidywalne wyniki -porównania są głównym powodem, że stosowanie == jest powszechnie uważane za złą -praktykę. Skomplikowane reguły konwersji są powodem trudnych do wyśledzenia błędów.

- -

Ponadto koercja ma również wpływ na wydajność, Na przykład gdy typ String musi zostać -przekształcony na typ Number przed porównaniem z drugą liczbą.

- -

Operator ścisłej równości

- -

Operator ścisłej równości składa się z trzech znaków "równa się": ===

- -

Działa on dokładnie tak jak normalny operator równości, z jednym wyjątkiem - nie -dokonuje koercji typów przed porównaniem.

- -
""           ===   "0"           // false
-0            ===   ""            // false
-0            ===   "0"           // false
-false        ===   "false"       // false
-false        ===   "0"           // false
-false        ===   undefined     // false
-false        ===   null          // false
-null         ===   undefined     // false
-" \t\r\n"    ===   0             // false
-
- -

Powyższe rezultaty są o wiele bardziej przejrzyste. Powoduje to "ustatycznienie" -języka do pewnego stopnia oraz pozwala na wprowadzenie optymalizacji porównań -obiektów o różnych typach.

- -

Porównywanie obiektów

- -

Mimo że oba operatory == i === nazywane są operatorami równościowymi, -to zachowują się różnie, gdy jednym z operandów jest obiekt typu Object.

- -
{} === {};                   // false
-new String('foo') === 'foo'; // false
-new Number(10) === 10;       // false
-var foo = {};
-foo === foo;                 // true
-
- -

Oba operatory porównują toższmość a nie równość, czyli będą porównywać czy -jeden i drugi operand jest tą samą instancją obiektu (podobnie jak operator -is w Pythonie i porównanie wskaźników w C).

- -

Wnioski

- -

Zaleca się, aby używać tylko operatora ścisłej równości. W sytuacjach gdy -potrzebna jest koercja (porównanie obiektów różnych typów), konwersja powinna -być dokonana jawnie, a nie pozostawiona trudnym regułom koercji -obowiązującym w języku.

Operator typeof

Operator typeof (razem z operatorem instanceof) jest -prawdopodobnie najwiekszą wadą konstrukcji języka JavaScript. Posiada on praktycznie
-same wady.

- -

Mimo że instanceof ma swoje wady to nadal ma ograniczone zastosowanie w praktyce, -natomiast typeof ma tylko jeden praktyczny przypadek użycia, który na dodatek -nie jest związany z sprawdzaniem typu obiektu.

- - - -

Tablica typów JavaScript

- -
Wartość             Klasa      Typ
--------------------------------------
-"foo"               String     string
-new String("foo")   String     object
-1.2                 Number     number
-new Number(1.2)     Number     object
-true                Boolean    boolean
-new Boolean(true)   Boolean    object
-new Date()          Date       object
-new Error()         Error      object
-[1,2,3]             Array      object
-new Array(1, 2, 3)  Array      object
-new Function("")    Function   function
-/abc/g              RegExp     object (function w Nitro i V8)
-new RegExp("meow")  RegExp     object (function w Nitro i V8)
-{}                  Object     object
-new Object()        Object     object
-
- -

W powyższej tabeli Typ odnosi się do wartości zwracanej przez operator typeof. -Wyraźnie widać, że zwracane wartości w ogóle nie są spójne.

- -

Klasa odnosi sie do wartości wewnętrznej właściwości [[Class]] obiektu.

- - - -

W celu uzyskania wartości właściwości [[Class]] trzeba skorzystać z metody -toString z Object.prototype.

- -

Klasa obiektu

- -

Specyfikacja zawiera dokładnie jeden sposób dostepu do wartości [[Class]], -wykorzystując Object.prototype.toString.

- -
function is(type, obj) {
-    var clas = Object.prototype.toString.call(obj).slice(8, -1);
-    return obj !== undefined && obj !== null && clas === type;
-}
-
-is('String', 'test'); // true
-is('String', new String('test')); // true
-
- -

Powyższy przykład wywołuje Object.prototype.toString z wartością -this ustawioną na obiekt, dla której wartość właściwości -[[Class]] ma zostać odczytana.

- - - -

Testowanie niezdefiniowania zmiennej

- -
typeof foo !== 'undefined'
-
- -

Powyższy kod sprawdza czy foo została faktycznie zadeklarowana czy też nie. -Próba odwołania się do zmiennej spowodowała by wyrzucenie błędu ReferenceError. -Jest to jedyne praktyczne wykorzystanie operatora typeof.

- -

Wnioski

- -

W celu sprawdzenia typu obiektu zalecane jest skorzystanie z -Object.prototype.toString, ponieważ jest to jedyny wiarygodny sposób. Jak -pokazano w powyższej tabeli typów, niektóre wartości zwracane przez typeof nie -są zdefiniowane w specyfikacji, co za tym idzie mogą się różnić w różnych -implementacjach.

- -

O ile nie operator typeof nie jest użyty do sprawdzania czy zmienna została -zdefiniowana, powinien być unikany jeśli to tylko możliwe.

Operator instanceof

Operator instanceof porównuje konstruktory obiektów przekazanych jako operendy. -Jest on użyteczny jedynie do porównywania obiektów utworzonych klas. Stosowanie -go na wbudowanych typach jest praktycznie tak samo bezużyteczne, jak operatora -typeof.

- -

Porównywanie obiektów utworzonych klas

- -
function Foo() {}
-function Bar() {}
-Bar.prototype = new Foo();
-
-new Bar() instanceof Bar; // true
-new Bar() instanceof Foo; // true
-
-// poniżej kod który przypisuje do Bar.prototype obiekt funkcji Foo
-// a nie faktyczną instancję Foo
-Bar.prototype = Foo;
-new Bar() instanceof Foo; // false
-
- -

Stosowanie instanceof na natywnych typach

- -
new String('foo') instanceof String; // true
-new String('foo') instanceof Object; // true
-
-'foo' instanceof String; // false
-'foo' instanceof Object; // false
-
- -

Jedną ważną rzeczą, którą należy zauważyć jest to, że instanceof nie zadziała -na obiektach, które pochodzą z różnych kontekstów JavaScript (np. z różnych -dokumentów wewnątrz przeglądarki), ponieważ ich konstruktory nie będą tymi -samymi obiektami.

- -

Wnioski

- -

Operator instanceof powinien być używany wyłącznie podczas korzystania z obiektów -klas utworzonych, które były zdefiniowane w tym samym kontekscie JavaScriptowym. -Podobnie jak operator typeof, należy unikać korzystania -z tego operatora w innych sytuacjach.

Rzutowanie typów

JavaScript jest językiem słabo typowanym. Co za tym idzie, będzie stosować koercję -typów gdziekolwiek jest to możliwe.

- -
// te zwracają true
-new Number(10) == 10; // Number.toString() zostanie przekształcone
-                      // z powrotem do liczby
-
-10 == '10';           // stringi zostaną przekształcone do typu Number
-10 == '+10 ';         // kolejne wariacje
-10 == '010';          // i następne
-isNaN(null) == false; // null zostanie przekształcony do 0
-                      // który oczywiście nie jest NaN
-
-// poniższe zwracają false
-10 == 010;
-10 == '-10';
-
- - - -

Aby uniknąć powyższych problemów, należy koniecznie korzystać ze -ściełego operatora równości. Mimo, że pozwala to uniknąć wiele -typowych problemów to nadal istnieje wiele innych, które powstają na bazie słabego -typowania języka JavaScript.

- -

Konstruktory typów wbudowanych

- -

Konstruktory typów wbudowanych, takich jak Number lub String, zachowują się -inaczej kiedy są poprzedzone słowem kluczowym new a inaczej kiedy nie są.

- -
new Number(10) === 10;     // False, Object i Number
-Number(10) === 10;         // True, Number i Number
-new Number(10) + 0 === 10; // True, ponieważ dokonano jawnej konwersji
-
- -

Korzystanie z wbudowanych typów jak Number jako konstruktora tworzy nowy obiekt -typu Number, natomiast opuszczenie słowa kluczowego new powoduje, że funkcja -Number zachowuje się jak konwerter.

- -

Ponadto, użycie literałów lub wartości nieobiektowych zaowocuje jeszcze większą -ilością rzutowań (koercją) typów.

- -

Najlepszym rozwiązaniem jest jawne rzutowanie do jednego z trzech typów.

- -

Rzutowanie do typu String

- -
'' + 10 === '10'; // true
-
- -

Konkatenacja pustego stringu i wartości powoduje rzutowanie do typu String.

- -

Rzutowanie do typu Number

- -
+'10' === 10; // true
-
- -

Zastosowanie unarnego operatora + spowoduje rzutowanie do typu Number.

- -

Rzutowanie do typu Boolean

- -

Używając dwukrotnie operatora negacji, dowolna wartość może zostać zrzutowana -do typu Boolean

- -
!!'foo';   // true
-!!'';      // false
-!!'0';     // true
-!!'1';     // true
-!!'-1'     // true
-!!{};      // true
-!!true;    // true
-

Jądro

Dlaczego nie należy używać eval?

Funkcja eval uruchomi podany string jako kod JavaScript w lokalnym zasięgu (scopie).

- -
var foo = 1;
-function test() {
-    var foo = 2;
-    eval('foo = 3');
-    return foo;
-}
-test(); // 3
-foo; // 1
-
- -

Niestaty, eval zostanie wykonana w lokalnym zasięgu tylko wtedy, gdy zostanie wywołana -bezpośrednio i nazwa wywoływanej funkcji równa sie eval.

- -
var foo = 1;
-function test() {
-    var foo = 2;
-    var bar = eval;
-    bar('foo = 3');
-    return foo;
-}
-test(); // 2
-foo; // 3
-
- -

Należy unikać stosowania eval o ile to tylko możliwe. W 99.9% przypadków można -osiągnąć ten sam efekt nie używając eval.

- -

eval w przebraniu

- -

Funkcje wykonywane po upływie czasu setTimeout i setInterval -mogą przyjąć string jako pierwszy argument. String ten zawsze będzie wykonywany -w globalnym zasięgu, ponieważ funkcja eval jest w tym wypadku wywoływana pośrednio.

- -

Problemy z bezpieczeństwem

- -

Funkcja eval jest również problematyczna od strony bezpieczeństwa, ponieważ -wykonuje każdy kod, który zostanie do niej przekazany i nigdy nie należy -jej używać na stringach nieznanego lub niezaufanego pochodzenia.

- -

Wnioski

- -

Funkcja eval nie powinna być w ogóle używana. Każdy kod, który jej używa -powinien zostać sprawdzony pod względem działania, wydajności i bezpieczeństwa. -W przypadku gdy użycie eval jest niezbędne do działania, wówczas taki kod -należy ponownie przemyśleć i ulepszyć aby nie wymagał użycia eval.

undefined i null

JavaScript ma dwie różne wartości dla pustych wartości, bardziej użyteczną -z tych dwóch jest undefined.

- -

Wartość undefined

- -

undefined jest typem z dokładnie jedną wartością: undefined.

- -

Język również definiuje globalną zmienną, która ma wartość undefined - zmienna -ta jest nazwana undefined. Jednakże jest to zmienna a nie stała, czy słowo -kluczowe. Oznacza to, że możliwe jest nadpisanie wartości tej zmiennej.

- - - -

Kilka przykładów kiedy wartość undefined jest zwracana:

- -
    -
  • dostęp do (niemodyfikowalnej) zmiennej globalnej undefined,
  • -
  • wyjście z funkcji, która nie ma deklaracji return,
  • -
  • deklaracja return, która nic jawnie nie zwraca,
  • -
  • poszukiwanie nieistniejącej właściwości,
  • -
  • parametr funkcji, który nie został jawnie przekazany podczas wywołania funkcji,
  • -
  • wszystko czemu została przypisana wartość undefined.
  • -
- -

Obsługa przypadku zmiany wartości undefined

- -

Ponieważ globalna zmienna undefined zawiera tylko kopię prawdziwej wartości typu -undefined, przypisanie nowej wartości do tej zmiennej nie zmienia wartości -typu undefined.

- -

Jednak aby porównać coś z wartością undefined, trzeba odczytać wartość undefined.

- -

Aby uchronić swój kod przed możliwym nadpisaniem zmiennej undefined, korzysta -się z powszechnej techniki dodania dodatkowego parametru do -anonimowego wrappera, do którego nie zostanie przekazany -argument.

- -
var undefined = 123;
-(function(something, foo, undefined) {
-    // undefined o lokalnym zasięgu znowu 
-    // odnosi się do poprawnej wartości
-
-})('Hello World', 42);
-
- -

Kolejnym sposobem na osiągnięcie tego samego efektu jest użycie deklaracji zmiennej -wewnątrz wrappera.

- -
var undefined = 123;
-(function(something, foo) {
-    var undefined;
-    ...
-
-})('Hello World', 42);
-
- -

Jedyną różnicą pomiędzy tymi sposobami są dodatkowe 4 bajty przeznaczone na słowo -kluczowe var i spację po nim.

- -

Zastosowanie null

- -

Podczas gdy undefined w kontekście języka jest używany jak null w sensie -tradycyjnych języków, null w JavaScript (jako literał i jako typ) jest po -prostu kolejnym typem danych.

- -

Jest wykorzystywany we wnętrzu JavaScript (np. deklaracji końca łańcucha prototypów -poprzez ustawienie Foo.prototype = null), ale prawie w każdym przypadku można go -zastąpić przez undefined.

Automatyczne wstawianie średnika

Mimo że JavaScript ma składnię podobną do języka C, to nie wymusza stosowania -średników w kodzie źródłowym. Istnieje możliwość ich pominięcia.

- -

JavaScript nie jest językiem bez średników, tak na prawdę potrzebuje -średników aby zinterpretować kod źródłowy. Jednakże parser JavaScript -automatycznie wstawia średniki o ile napotka błąd parsowania związany z -brakiem średnika.

- -
var foo = function() {
-} // błąd parsowania, oczekiwany był w tym miejscu średnik
-test()
-
- -

Parser dodaje średnik, i próbuje jeszcze raz sparsować skrypt.

- -
var foo = function() {
-}; // bez błędu parser kontynuuje
-test()
-
- -

Automatyczne wstawianie średników jest uważane za jeden z największych błędów -konstrukcji języka, ponieważ może ono zmienić zachowanie kodu.

- -

Jak działa wstawianie

- -

Kod poniżej nie ma żadnych średników, więc parser zdecyduje, w których miejscach -je wstawi.

- -
(function(window, undefined) {
-    function test(options) {
-        log('testing!')
-
-        (options.list || []).forEach(function(i) {
-
-        })
-
-        options.value.test(
-            'long string to pass here',
-            'and another long string to pass'
-        )
-
-        return
-        {
-            foo: function() {}
-        }
-    }
-    window.test = test
-
-})(window)
-
-(function(window) {
-    window.someLibrary = {}
-
-})(window)
-
- -

Poniżej znajduje się rezultat "zgadywania" parsera.

- -
(function(window, undefined) {
-    function test(options) {
-
-        // Nie wstaniony średnik, linie zostały połączone
-        log('testing!')(options.list || []).forEach(function(i) {
-
-        }); // <- wstawiony
-
-        options.value.test(
-            'long string to pass here',
-            'and another long string to pass'
-        ); // <- wstawiony
-
-        return; // <- wstawiony, psując deklarację return
-        { // potraktowane jako definicja bloku
-
-            // etykieta oraz pojedyncze wyrażenie
-            foo: function() {} 
-        }; // <- wstawiony
-    }
-    window.test = test; // <- wstawiony
-
-// Kolejna połączona linia
-})(window)(function(window) {
-    window.someLibrary = {}; // <- wstawiony
-
-})(window); //<- wstawiony
-
- - - -

Parser drastycznie zmienił działanie powyższego kodu. W niektórych przypadkach -zmienił go źle.

- -

Nawiasy

- -

W przypadku, gdy w następnej linii znajduje się nawias, parser nie wstawi -średnika.

- -
log('testing!')
-(options.list || []).forEach(function(i) {})
-
- -

Kod ten zostanie zmieniony w poniższą linię.

- -
log('testing!')(options.list || []).forEach(function(i) {})
-
- -

Jest bardzo prawdopodobne, że log nie zwróci fukcji. Co za tym idzie -powyższy kod wyrzuci błąd TypeError oznajmując, że undefined is not a -function - undefined nie jest funkcją.

- -

Wnioski

- -

Zaleca się, aby nigdy nie pomijać średników, pozostawiać nawias otwierający -w tej samej linii co odpowiadająca mu definicja i nigdy nie pozostawiać deklaracji -if / else bez nawiasów - nawet, jeżeli są jednolinijkowe. Wszystkie te uwagi nie -tylko pomagają poprawić spójność kodu, ale też zapobiegają zmianie działania -kodu przez parser JavaScript.

Inne

setTimeout i setInterval

Ponieważ JavaScript jest asynchroniczny, istnieje możliwość zaplanowania wykonania -funkcji przy użyciu funkcji setTimeout i setInterval. -Since JavaScript is asynchronous, it is possible to schedule the execution of a -function by using the setTimeout and setInterval functions.

- - - -
function foo() {}
-var id = setTimeout(foo, 1000); // zwraca liczbę typu Number > 0
-
- -

Powyższe wywołanie setTimeout zwraca ID budzika i planuje wywołanie foo za -około tysiąc milisekund. foo zostanie wykonana dokładnie jeden raz.

- -

Nie ma pewności, że kod zaplanowany do wykonania wykona się dokładnie po -upłynięciu zadanego czasu podanego jako parametr do setTimeout, ponieważ zależy -to od dokładności zegara w silniku JavaScript, który wykonuje kod oraz od tego, -że inny kawałek kodu może zablokować wątek, ponieważ JavaScript jest tylko -jednowątkowy.

- -

Funkcja, która została przekazana jako pierwszy parametr zostanie wykonana w -globalnym zasięgu, co oznacza, że this wewnątrz tej funkcji -będzie wskazywać na obiekt global.

- -
function Foo() {
-    this.value = 42;
-    this.method = function() {
-        // this wskazuje na obiekt global
-        console.log(this.value); // wypisze undefined
-    };
-    setTimeout(this.method, 500);
-}
-new Foo();
-
- - - -

Kolejkowanie wywołań z setInterval

- -

Podczas gdy setTimeout wywołuje podaną funkcję tylko raz, setInterval - -jak wskazuje nazwa - będzie wykonywać funkcję w odstępach czasowych co X -milisekund. Jednakże korzystanie z tej funkcji jest odradzane.

- -

Kiedy wykonywany kod zablokuje możliwość uruchomienia zaplanowanej funkcji, -setInterval będzie próbować uruchamiać daną funkcję, co będzie powodować -kolejkowanie wykonania tej samej funkcji kilkukrotnie. Może się to zdażyć -szczególnie przy krótkim interwale.

- -
function foo(){
-    // coś co blokuje wykonanie na 1 sekundę 
-}
-setInterval(foo, 100);
-
- -

W powyższym kodzie kod foo zostanie wywołany tylko raz i zablokuje wywołanie na -jedną sekundę.

- -

Podczas, gdy funkcja foo blokuje wykonanie, setInterval będzie planować kolejne -wywołania foo. W momencie, gdy pierwsze wywołanie foo się zakończy, -w kolejce do wywołania będzie już czekało kolejne dziesięć wywołań tej funkcji.

- -

Radzenie sobie z możliwymi blokadami

- -

Najprostszą, jak również najbardziej kontrolowaną sytuacją, jest użycie setTimeout -wewnątrz wywoływanej funkcji.

- -
function foo(){
-    // coś co blokuje wykonanie na 1 sekundę
-    setTimeout(foo, 100);
-}
-foo();
-
- -

Powyższy kod nie tylko hermetyzuje wywołanie setTimeout, ale też zapobiega -kolejkowaniu wywołań funkcji i daje dodatkową kontrolę. W tym przypadku funkcja -foo może zdecydować czy powinna się wywołać ponownie, czy też nie.

- -

Ręczne usuwanie budzików

- -

Usuwanie budzików i interwałów dokonywane jest przez przekazanie odpowiedniego ID -do clearTimeout lub clearInterval, w zależności z jakiej funkcji zostało -zwrócone ID.

- -
var id = setTimeout(foo, 1000);
-clearTimeout(id);
-
- -

Usuwanie wszystkich budzików

- -

Ponieważ nie istnieje wbudowana metoda usuwania wszystkich budzików i/lub -interwałów, do osiągnięcia tego efektu konieczne jest użycie metody 'brute force'.

- -
// usunięcie "wszystkich" budzików 
-for(var i = 1; i < 1000; i++) {
-    clearTimeout(i);
-}
-
- -

Nadal mogą istnieć jakieś budziki, na które powyższy kawałek kodu nie zadziała. -Ponieważ ID było z innego przedziału, zamiast korzystania z metody brute force, -zaleca się śledzić wszystkie numery ID budzików, aby można je było usunąć.

- -

Ukryte wykorzystanie eval

- -

Do setTimeout i setInterval można również przekazać string jako pierwszy -parametr zamiast obiektu funkcji, jednakże nigdy nie należy korzystać z tej -możliwości, ponieważ wewnętrznie setTimeout i setInterval wykorzystują eval.

- - - -
function foo() {
-    // zostanie wykonane 
-}
-
-function bar() {
-    function foo() {
-        // nigdy nie zostanie wywołane
-    }
-    setTimeout('foo()', 1000);
-}
-bar();
-
- -

Ponieważ eval nie zostało wywołane w tym przypadku wprost, to -string przekazany do setTimeout zostanie uruchomiony w zasięgu globalnym. -Co za tym idzie, lokalna zmienna foo z zasięgu bar nie zostanie użyta.

- -

Kolejnym zaleceniem jest niestosowanie stringów do przekazywania argumentów -do funkcji, która ma zostać wywołana przez budzik.

- -
function foo(a, b, c) {}
-
-// NIGDY nie należy tak robić 
-setTimeout('foo(1,2, 3)', 1000)
-
-// zamiast tego należy skorzystać z anonimowej funkcji
-setTimeout(function() {
-    foo(a, b, c);
-}, 1000)
-
- - - -

Wnioski

- -

Nigdy nie należy przekazywać stringu jako parametru do setTimeout lub -setInterval. Jest to wyraźną oznaką bardzo złego kodu. Jeżeli potrzebne jest -przekazanie argumentów do funkcji, należy skorzystać z anonimowej funkcji i -wewnątrz niej dokonać przekazania argumentów.

- -

Ponadto, należy unikać korzystania z setInterval, ponieważ planista może -zablokować wykonanie JavaScriptu.

\ No newline at end of file