Autorzy
Ten przewodnik jest dziełem dwóch uroczych użytkowników Stack Overflow, -Ivo Wetzel (Treść) oraz Zhang Yi Jiang (Projekt).
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 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.
Ten przewodnik jest dziełem dwóch uroczych użytkowników Stack Overflow, -Ivo Wetzel (Treść) oraz Zhang Yi Jiang (Projekt).
JavaScript Garden znajduje się na serwerach GitHub, ale dzięki wsparciu -Cramer Rozwoju posiadamy mirror na serwerze JavaScriptGarden.info.
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.
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 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};
-
-
-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.
- -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.
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.
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
.
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.
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.
- -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 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
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
-
-
-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.
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
.
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.
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.
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.
- -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() {}
-
-
-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.
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.
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.
this;
-
-
-Używanie this
w globalnym zasięgu, zwróci po prostu referencję do obiektu global.
foo();
-
-
-Tutaj this
również będzie wkazywało na obiekt global
test.foo();
-
-
-W tym przypadku this
będzie wskazywało na test
.
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.
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
.
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ół.
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ę.
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.
- -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.
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
.
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
.
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)
-}
-
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
.
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ść.
- -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);
-};
-
-
-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);
-
-
-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 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ść this
zostanie wykorzystany obiekt global.
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
.
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.
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.
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
.
// 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.
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.
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 = {};
-}
-
-
-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.
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(){}());
-
-
-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.
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
.
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.
- -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ę.
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ść.
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);
-
-
-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.
JavaScript posiada dwa różne sposoby równościowego porównywania obiektów.
- -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 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.
- -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).
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.
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.
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
.
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.
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
.
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.
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.
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
-
-
-instanceof
na natywnych typachnew 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.
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.
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, 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.
- -'' + 10 === '10'; // true
-
-
-Konkatenacja pustego stringu i wartości powoduje rzutowanie do typu String.
- -+'10' === 10; // true
-
-
-Zastosowanie unarnego operatora + spowoduje rzutowanie do typu Number.
- -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
-
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 przebraniuFunkcje 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.
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.
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
.
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:
undefined
,return
,return
, która nic jawnie nie zwraca,undefined
.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.
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
.
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.
- -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.
- -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ą.
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.
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();
-
-
-
-
-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.
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.
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);
-
-
-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ąć.
- -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)
-
-
-
-
-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.