Wprowadzenie do JavaScript

ECMAScript 5, Web APIs, testowanie aplikacji

Bartosz Szczeciński

16.05.2018

@btmpl

medium.com/@baphemot

Bartosz Szczeciński

software developer, konsultant

https://szczecinski.eu

https://medium.com/@baphemot/

Cele

  • Kurs JavaScript dla programistów nie mających wcześniejszej styczności z JS
     
  • Interakcja JS z przeglądarką
     
  • Praca z serwerem zdalnym w technologii REST
     
  • Testowanie aplikacji JS przy użyciu mocha+karma

What the ... JavaScript?

https://www.youtube.com/watch?v=2pL28CcEijU

Wymagania

  • Komputer z dostępem do internetu
  • Przeglądarka internetowa 
  • Ulubiony edytor kodu

     
  • Podstawowa wiedza w zakresie języków programowania
  • Podstawy HTML / CSS

Logistyka

  • Wszystkie materiały są / będą dostępne on-line
     
  • Masz pytanie, komentarz lub wątpliwość? Podnieś rękę - nie musisz czekać do końca warsztatu.
     
  • Pytania dodatkowe / wykraczające poza zakres szkolenia? Sesja Q&A na końcu szkolenia, zapytaj mnie w przerwie albo na @btmpl
     
  • "Pan Szczeciński" to mój tata - możecie po prostu mówić mi po imieniu ;-)

JavaScript

JavaScript

JavaScript to jedno-wątkowy, lekki, interpretowany (lub korzystający z technologii JIT) język programowania.

 

Mimo iż JS oferuje prototypowy model pracy z obiektami umożliwia on pracę z wieloma paradygmatami tj. programowanie proceduralne oraz funkcyjne.

JavaScript

Początki języka

Autorem języka JavaScrip jest Brendan Eich - w ciągu 10 dni, w 1995 roku pierwszą wersję języka znanego dziś jako "JavaScript".

 

Początkowo język ten miał działać w przeglądarce i dać przewagę przeglądarce Netscape Navigator nad Internet Explorerem i Microsoftem.

 

Dzisiaj "JavaScript" jest wielo-platformowym językiem działającym w silnikach oddzielonych od przeglądarki i na wszelkiego rodzaju platformach.

JavaScript

ECMAScript i wersje JS

JavaScript nie jest defacto językiem a implementacją języka ECMA-262 (ECMAScript) zdefiniowanego przez ECMA. Dlatego też często spotkamy się z określeniami "ES", "ECMAScript" etc.

 

Jest to szczególnie istotne, jako iż JavaScript jest znakiem towarowym korporacji Oracle, która wielokrotnie pokazywała, że jest gotowa bronić swojej "własności intelektualnej".

JavaScript

ECMAScript i wersje JS

Aktualna wersja ECMAScript to ES8.

 

ES5 pozostaje wciąż istotną częścią ekosystemu, jako iż jest to najnowsza wersja wspierana natywnie przez większość dostępnych na rynku przeglądarek.

 

Zmiany pomiędzy ES5 a ES6 są dosyć istotne. Kolejne wersje przynoszą już mniejsze zmiany.

JavaScript

JS dzisiaj i jutro

Obecnie standardem jest wciąż dostarczanie do przeglądarek kodu JavaScript w wersji ES5.

 

Dzięki narzędziom takim jak Babel możliwe jest tworzenie kodu w nowszych standardach (w tym z wykorzystaniem notacji niedostępnej jeszcze w standardzie ECMAScript) i następnie "transpilowanie" go do ES5.

 

Na warsztatach skupimy się jednak na tworzeniu bezpośrednio w ES5.

Składnia

Składnia

Składnia jest to zbiór słów kluczowych, wyrażeń, operatorów instrukcji i wartości składających się na kod źródłowy aplikacji w danym języku programowania.

Przykładowy kod JS

 

var me = {
    age: 34,
    name: 'Bartosz'
};

function isPersonOfLegalAge(person) {
    if (person.age >= 18) {
        return true;
    } else {
        return false;
    }
}

if (isPersonOfLegalAge(me)) {
    me = withBeer(me);
} else {
    me = withCola(me);
}

me.cheers();

Kodowanie znaków

 

if (isPersonOfLegalAge(me)) {
    me = with🍺(me);
} else {
    me = with🥤(me);
}
'cola'.length // 4
'🥤'.length // 2

JS wewnętrznie używa kodowania UTF-16

(chyba że autor silnika zdecyduje inaczej)

'🥤'.split('') // (2) ["�", "�"]

Powoduje to kilka problemów:

Więcej niż kilka ...

'👨‍👩‍👧‍👦 '.length; //

[...'👨‍👩‍👧‍👦 ']; //
11
"👨", "‍", "👩", "‍", "👧", "‍", "👦", " "

https://twitter.com/wesbos/status/769228067780825088

Dołączanie JS do stron HTML

<body>
    <h1>Witaj świecie</h1>
    <script>
        var wspieramJs = true;
    </script>
</body>

Osadzanie bezpośrednio w kodzie strony:

  • parsowany synchronicznie (blokuje renderowanie strony)
  • wystąpienie błędu zatrzymuje parsowanie kodu w danym bloku

Dołączanie JS do stron HTML

<body>
    <h1>Witaj świecie</h1>
    <script src="./script.js"></script>
</body>

Dołączenie zewnętrznego pliku z kodem JavaScript:

  • domyślnie parsowany synchronicznie
  • wystąpienie błędu powoduje zaprzestanie parsowania pliku
  • rekomendowanym jest używanie rozszerzenia .js ale w zależności od konfiguracji serwera i przeglądarki może to być dowolne rozszerzenie
  • domyślnie podczas wczytywania pliku następuje "zawieszenie się" strony
  • skrypty możemy osadzać w <head> jeżeli chcemy by były dostępne "od razu jak tylko strona zacznie się renderować" albo pod koniec <body> w przeciwnym wypadku

Dołączanie JS do stron HTML

<script src="./script.js" async></script>
<script src="./script.js" defer></script>
  • atrybut async spowoduje równoległe pobranie pliku, ale po pobraniu może "zawiesić" przeglądarkę na czas jego wykonania
  • brak gwarancji kolejności
  • atrybut defer działa podobnie jak async, ale kod zostanie wykonany dopiero w momencie kiedy strona będzie wyrenderowana
  • wykonane w kolejności deklaracji

Komentarze

// komentarz w jednej linijce

/*
    komentarz blokowy
 */

/**
 * popularna forma komentarza blokowego
 */
// <!-- 
var name = 'Bartosz';
// -->

Komentarz dla starych IE, które w innym wypadku wyświetlały kod jako treść strony

ASI

Automated Semicolon Insertion

var _name = 'Bartosz';
var _age = 34;

var name = 'Bartosz'
var age = 34
var a = 3;

var a
a
=
3
var a
a
=
3
// a = 3

var i = 0, j = 0

i
++
j

i //
j //
function test() {
    return
    ( 1 + 1 )
}

test(); // 

var i = 0
[1,2,3].pop(); //
0
1
undefined
call to pop of undefined

Zmienne

poprawne nazewnictwo

  • zmienna to rodzaj "nazwanego pudełka" w którym przetrzymywane są dane
  • deklaracja zmiennej poprzedzona jest słowem var
  • nazwa zmiennej musi składać się z minimum 1 znaku
  • musi zaczynać się od:
    - litery
    - znaku _
    - znaku $
  • kolejnymi znakami mogą być litery, znaki diakrytyczne, cyfry, _ i $ (oraz kilka "niewidzialnych" znaków)

Zmienne

zastrzeżone słowa

break do instanceof typeof
case else new var
catch finally return void
continue for switch while
debugger function this with
default if throw  
delete in try
class enum extends super
const export import
implements let private public yield
interface package protected static  

Typy danych

Typy danych

Struktura obiektu danych informująca interpreter lub kompilator o sposobie w jaki developer zamierza wykorzystać dane (operować na nich), dzięki czemu możliwa jest odpowiednia optymalizacja pamięci, w której dane są przechowywane.

Typy proste

undefined

Domyślna wartość zmiennej, nadawana w momencie kiedy nie podamy żadnej wartości, pole nie istnieje, lub wyraźnie nadamy wartość "undefined"

var age; // undefined
var name = undefined;

var me = {

};

me.name; // undefined

Typy proste

Boolean

Prosty typ danych przyjmujący wartość "true" lub "false".

var isOfLegalAge = false;
var isHappy = true;

Typy proste

Number

Wartości numeryczne w JS traktowane są jako liczby zmiennoprzecinkowe oddzielone znakiem kropki.

var age = 34;
var height = 185.5;

height = 186,1; 

Przecinek jest operatorem w JS! Wynik to 186!

0.1 + 0.2 = ?
0.1 + 0.2 = 0.30000000000000004

https://0.30000000000000004.com/

// 1

Typy proste

Number - stałe

JS deklaruje także kilka stałych pomocnych przy pracy z wartościami liczbowymi:

0 / 0; // NaN
5 / 0; // Infinity

Typy proste

String

Łańcuchy znaków w JS mogą być zapisane przy użyciu znaku " lub ' - różnicą jest konieczność "escapowania" niektórych znaków specjalnych

var name = 'Bartosz';

var lastName = 'Szczeciński';

var pseudonim = 'BTM';

// var fancyName = "Bartosz "BTM" Szczeciński"; 
// var fancyName = "Bartosz \"BTM\" Szczeciński";
var fancyName = 'Bartosz "BTM" Szczeciński'

fancyName = name + ' ' + pseudonim + ' ' + lastName;

var multiLine = 'Tekst w wielu linijkach' + 
    'musi być łączony znakiem +';

Typy proste

Null

Wartość logiczna oznaczając brak wartości, ale nie brak danych.

 

Często używa się odróżnienia "undefined" i "null" (lub 0, false) dla oznaczenia, że dane zostały zweryfikowane (np. pobrane z serwera) ale ich wartość jest nieznana tak, by nie próbować pobrać ich ponownie.

Typy złożone

Object

Typ złożony pozwalający na przechowywanie w nim wielu innych typów prostych oraz złożonych.

var person = {
    name: 'Bartosz',
    age: 34
};

var workshop = {
    leadBy: person,
    topic: 'Intro to JS',
    attendees: 10
};

workshop.attendees; // 10
workshop.leadBy.name; // 'Bartosz'

Typy złożone

Array

  • Typ złożony pozwalający na przechowywanie wielu typów prostych lub złożonych w określonej kolejności.
  • Tablice są numerowane od 0
  • Tablice w JS nie są typowymi tablicami znanymi z innych języków, są one podobne do Map (oraz są Obiektami)
var tablica0 = new Array(1,2,3,4);
var tablica1 = [1,2,3,4];

var tablica2 = [];
tablica2[0] = 1;
tablica2[2] = 3;
tablica2; // [1, , 3]

tablica2.test = 4;
tablica2; // [1, empty, 3, test: 4]

Typy złożone

Function

Funkcje w JS także są obiektami
dowiemy się o tym więcej później :))

Typy złożone predefiniowane

Date

Obiekt Date pozwala na podstawową pracę z datami. Kolejne wersje JS rozszerzają możliwości dodając obiekty daty w danym locale etc.

// poniższe przykłady zakładają datę 7 maja 2018

var date = new Date();
date; // Mon May 7 2018 20:53:52 GMT+0200 (Środkowoeuropejski czas letni)

date.getYear(); // 
date.getFullYear(); // 

date.getDay(); // 
date.getDate(); // 
date.getMonth(); // 

118

2018

4

1 - poniedziałek

 4

Typy złożone predefiniowane

Math

Obiekt Math posiada kilka funkcji oraz stałych pomocnych przy pracach ze wzorami i operacjami matematycznymi.

Math.PI; // 3.141592653589793
Math.E; // 2.718281828459045

Math.round(2.65); // 3
Math.floor(2.65); // 2

Math.random(); // 0-1

JSON

JSON (JavaScript Object Notation) nie jest w prawdzie typem danych, ale jest używany na tyle często, że warto o nim wspomnieć. Notacja JSON pozwala na zmianę typów prostych / obiektów na string i późniejsze odtworzenie.

var person = {
    name: 'Bartosz',
    age: 34
}

var asJson = JSON.stringify(person); // "{"name":"Bartosz","age":34}"

var person2 = JSON.parse(asJson);

Podczas zmiany na JSON usuwane są obiekty, których nie można zserializować.

W przypadku wystąpienia cyklicznych referencji silnik zgłosi błąd.

Sprawdzanie typu

typeof

typeof true // 
typeof 1 //
typeof 'test' //
typeof { name: 'Bartosz' } //
typeof null // 
typeof [1,2,3] //
typeof function() {} //

typeof NaN //
typeof "1" + 1 //

boolean

number

string

object

object

string1

function

object

number

Do sprawdzania typu danych należy używać operatora "typeof", który zwraca tekstową nazwę typu wartości po prawej stronie operatora.

Konwersja między typami

Przy większości operacji JS jest "sprytny" i postara się samodzielnie określić jaki typ powinny mieć zmienne tak, by operacja "miała sens".

 

  • jeżeli wartość jest prymitywem - nie nic rób
  • spróbuj wywołać obj.toString()
  • spróbuj wywołać obj.valueOf()
  • jeżeli nic nie pomoże, TypeError

Konwersja między typami

Nie powinniśmy jednak na tym polegać.

var a = 1;
var b = "2";
a + b // 

var a = 1;
var b = [1,2,3];
a + b //

1 + {} //
{} + 1 // 

"12"

"11,2,3"

"1[object Object]"

"1"

Konwersja między typami

// na number
typeof parseInt("1"); // number 1
typeof Number("1"); // number 1
typeof Number("one"); // number NaN

// na string
typeof String(1); // string
typeof ("" + 1); // string "1"

// na boolean
typeof "true"; // string
typeof !"true"; // boolean true
typeof !!"true"; // boolean false

Przy większości operacji JS jest "sprytny" i postara się samodzielnie określić jaki typ powinny mieć zmienne tak, by operacja miała sens.

 

Nie powinniśmy jednak na tym polegać.

Operatory

Operatory

Element składni języka pozwalający na przypisywanie, modyfikowanie i porównywanie typów danych.

Przypisanie

  • W celu przypisania wartości do zmiennej używamy pojedynczego znaku równości.
  • Możemy przypisać wiele wartości w jednej linijce, oddzielając kolejne operacje przypisania przecinkiem.
var a = 1;

var a = 1, b = 2, c = 3;
  • Istnieją także kombinowane operatory przypisania wykonujące operacje matematyczne i logiczne
var a = 5;

var b = 0;

b += 5; // b = b + 5;
b -= 2; // b = b - 5;

a |= 2; // a = a | 2 = 7

Porównanie

W celu porównania dwóch wartości możemy użyć operatorów matematycznych: == <= >= < > !=

2 == 2 //

2 < 2 //

2 > 1 //

1 == "1" //

1 < 2 < 3 //

3 > 2 > 1 //

true

false

true

true

true

false

2 == "2"; // true

2 === "2"; // false

2 <== 2 // true
NaN == NaN; //

false

Porównanie

Porównywać można tylko typy proste - porównywanie typów złożonych porównuje ich "referencje"

[1, 2, 3] === [1, 2, 3]; // false
{ age: 34 } === { age: 34 }; // false

var array = [1, 2, 3];
var array2 = array;
array === array2; // true

array2 = [1, 2, 3];
array === array2; // false

Dodawanie / łączenie

Operator + w zależności od kontekstu dokonuje operacji dodania lub połączenia wartości tekstowych obiektu.

1 + 2; // 3
1 + "2"; // 12
1 + [1, 2]; // "112"
(new Date()) + 1; "Mon May 10 2018 21:50:49 GMT+0200 (Środkowoeuropejski czas letni)1"

Operatory logiczne

JS oferuje oczywiście kilka operatorów logicznych.

var a = 0 || 1; // 1

var a = 0 && 1; // 

0

if (condition1 && condition2) {
  // oba założenia są prawdziwe
}

if (condition1 || condition2) {
  // przynajmniej jedno założenie jest prawdziwe
}

Operator "potrójny" lub "warunkowy"

ternary operator

Operator potrójny przydatny jest tam, gdzie chcemy wykonać prostą operację logiczną i przypisać jedną z 2 wartości do zmiennej.

var result = true ? "prawda" : "kłamstwo";

Oczywiście technicznie można je łączyć w bardziej zaawansowane formy, ale czy na pewno tego chcemy?

var result = test1() && test2() ? true : test3() ? "maybe" : false;

Przecinek

Operator przecinka powoduje wykonanie wyrażenia przed przecinkiem a następnie zwrócenie wartości po przecinku (niezależnie od wyniku wyrażenia przed).

 

Ponieważ zapis ten jest stosowany rzadko i może prowadzić do problemów ze zrozumieniem kodu nie zaleca się go stosować.

var a;
a = 186,1;
a; // 1

function test() {
  // usuń dane z bazy
}
a = test(),2;
a; // 2, a dane zostały usunięte ...

Operator bitowy

var a = 3;
var b = 6;
var c = a & b; // AND
/*
0011
0110
----
0010 = 2
*/

var c = a | b; // OR
/*
0011
0110
----
0111 = 7
*/

var c = a | b; // XOR
/*
0011
0110
----
0101 = 5
*/
1 << 2
/*
0001
0100 = 4
*/

8 >> 1
/*
1000
0010 = 2
*/

Wykonywanie kod dynamicznego

eval

Czasem może zachodzić potrzeba wykonania kodu dynamicznego, np. zbudowanego na podstawie danych uzyskanych od klienta. Zwyczajowo używa się wtedy np. skończonych maszyn stanu, ale można też użyć konstruktu języka - eval - pozwalającego na wykonanie dowolnego polecenia i zwrócenie wartości.

var wynik = eval('1 + 2');
wynik; // 3

Wykonywanie kod dynamicznego

eval

Kod wykonywany w eval ma dostęp do wszystkich zmiennych w swoim zasięgu, tak jakby był zapisany bezpośrednio w kodzie strony. Również tworzone w nim zmienne dodawane są do tego zakresu.

var test = 1;
eval('test++');
test; //2

eval('var mojaZmienna = 3');
mojaZmienna; // 3

Polecenia

Polecenia

Element składni instruujący język o sposobie przepływu danych i informujący o operacjach, które należy wykonać.

Kondycje

if / else / else if

Operatory kondycji logicznych if-then pozwalają na sterowanie przepływem aplikacji w oparciu o założenia, które zwracają wartość true lub false

function isOfLegalAge(age) {
    if (age < 18) 
        return 'Przepraszamy, funkcja dostępna tylko dla osób pełnoletnich.';
    else 
        return 'OK'
}
if (warunek1) 
    return 'wynik 1';
else if (warunek2) 
    return 'wynik 2';
else 
    return 'wynik3';

Istnieje możliwość łączenia kondycji w łańcuchy else-if:

Ćwiczenie

sprawdzanie wieku

https://github.com/BTMPL/es5-workshop

Bloki kodu

W przypadku kiedy gałąź kondycji powinna zawierać więcej niż jedno wyrażenie lub deklarację, należy utworzyć blok kodu ograniczony przez znaki { i }

if (warunek1) 
    return 'wynik 1';
else if (warunek2) {
    wykonajInneRzeczy();
    return 'wynik 2';
}
else {
    return 'wynik3';
}

Switch

W przypadku gdy warunek logiczny może przyjąć inne wartości niż true / false, zaleca się użycie operatora switch:

switch (dzienTygodnia) {
  case 1:
    return 'Poniedziałek';
    break;
  case 2:
    return 'Wtorek';
    break;
  case 3:
    return 'Środa';
    break;
  case 4:
    return 'Czwartek';
    break;
  case 5:
    return 'Piątek';
    break;
  case 6:
    return 'Sobota';
    break;
  case 7:
    return 'Niedziela';
    break;
}

Switch

W przypadku braku operatora "break" poszczególne case łączą się:

switch (dzienTygodnia) {
  case 1:
    console.log('Poniedziałek');
  case 2:
    console.log('Wtorek');
    break;
  case 3:
    console.log('Środa');
    break;
  case 4:
    console.log('Czwartek');
    break;
  case 5:
    console.log('Piątek');
    break;
  case 6:
    console.log('Sobota');
    break;
  case 7:
    console.log('Niedziela');
    break;
}

// dla 1 - Poniedziałek oraz Wtorek 

Switch

W przypadku braku odpowiedniego case, deklarujemy case "default":

switch (dzienTygodnia) {
  case 6:
  case 7:
    return 'WEEKEND!';
  default:
    return 'Niestety, czas do pracy :(';
}

// dla 6 lub 7 - "WEEKEND"
// dla pozostałych wartości "Niestety, czas do pracy :("

Pętle

for, while, do while, for in

for (var i = 0; i < 10 ; i++) {
  console.log(i); 
}
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

for (var i = 0, e = 10 ; i < e ; i++) {
  console.log(i);
}
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

for (var i = 10, e = 0 ; i > e ; i--) {
  console.log(i);
}
// 10, 9, 8, 7, 6, 5, 4, 3, 2, 1

var array = ['a', 'b', 'c'];
for(var i in array) {
  console.log();
}
// 0, 1, 2
var a = 0;
while(a < 10) {
  console.log(a);
  a = a + 1;
}
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

var a = 0;
do {
  console.log(a);
  a = a + 1;
} while (a < 10);
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
for (var i = 0; i < 10 ; i--) {
  console.log(i); 
}
// ?
Nieskończona pętla!

Pętle

przerwanie i kontynuowanie

Czasami może zachodzić potrzeba przerwania pętli w wypadku innym niż osiągnięcie jej założeń lub pominięcie jakiejś iteracji. W tym celu dysponujemy słowami kluczowymi break i continue

for(var i = 0 ; i < 10 ; i++) {
    if (i % 2 === 1) {
        // jeżeli licznik pętli jest nieparzysty, przejdź do kolejnej iteracji
        continue;
    }

    if (obliczWartosc(i) === 5) {
        // jeżeli spełniony jest dodatkowy warunek, przerwij całkowicie pętle
        break;
    }

    console.log(i);
}

With

Wyrażenie "with" pozwala na "wyciągnięcie" pól obiektu do wewnętrznego bloku tak, jakby były one zmiennymi. Z uwagi na niejednoznaczne przesłanianie się zmiennych nie zaleca się stosowania tego konstruktu.

var person = {
    name: 'Bartosz',
    age: 34
};

with (person) {
    console.log(name); // 'Bartosz'
    console.log(age); // 34
}

Ćwiczenie

odwracanie stringu

Funkcje

Funkcje

Zbiór wyrażeń i poleceń połączony o określonej nazwie (lub anonimowy) umożliwiający wielokrotne wywołanie bez potrzeby ponownej deklaracji i posiadający możliwość zwrócenia wyniku do punktu wywołania.

Deklarowanie

definiowanie

Istnieje kilka notacji deklarowania funkcji, z czego najczęściej spotyka się tylko 2 (o tym co je różni przeczytasz w "hoisting").

function test1() {
    return 'funkcja test1';
}

var test2 = function() {
    return 'funkcja test2';
}

var test3 = new Function('return test3');

Deklaracja funkcji wymaga podania ciała funkcji oraz opcjonalnie słowa kluczowego "function" lub wywołania konstruktora obiektu Function.

Wywoływanie

Zwyczajowo w celu wywołania funkcji używamy zmiennej, do której została ona przypisana i dodajemy znacznik () (w którym możemy przekazać opcjonalnie argumenty funkcji).

function test1() {
    console.log('funkcja test1');
}

test1(); // 'funkcja test1';

Zwracanie danych

Funkcja może zwrócić dowolny typ danych poprzedzając go słowem kluczowym return, z zaznaczeniem, że może ona zwrócić tylko raz i zwrócenie natychmiast kończy wykonywanie funkcji. Jeżeli chcesz zwrócić więcej danych, użyj tablicy lub obiektu.

function test1() {
    return 42;
}

var wynik = test1();
wynik // '42';

Funkcje czyste

Funkcjami czystymi (pure function) nazywamy funkcje, których wartość wyjściowa zależy zawsze tylko i wyłącznie od wartości wejściowej (nie wywołują one żadnych innych funkcji anie nie odczytują zmiennych innych niż argumenty) oraz nie modyfikują one żadnych innych danych w swoim zakresie ani argumentów.

var dwa = 2;
function test1() {
    return 40 + dwa; // funkcja nie jest już czysta
}

var wynik = test1();
wynik // '42';

function test2() {
    window.usunDaneZBazy(); // funkcja nie jest czysta, "efekt uboczny"
}

test2();

Wywoływanie

metody vs funkcje

Metodami nazywamy funkcje, które zostały przypisane do obiektu:

var person = {
    test: function() {
        return 'To jest funkcja "test" w obiekcie "person"
    }
};

person.test();

Wywoływanie

call, apply

Funkcje mogą być także wywołane używając metody 'call' lub 'apply' przypisanej do obiektu Function (więcej o ich zastosowaniu dowiesz się w rozdziale dot. Obiektów)

function test1() {
    return 'funkcja test1';
}

test1.call(); // 'funkcja test1'
test1.apply(); // 'funkcja test1'

Argumenty

Deklarując funkcję można określić ilość i nazwy parametrów, z jakimi może zostać ona wywołana. Jeżeli funkcja deklaruje jakąś zmienną, ale podczas jej wywoływania nie przekażemy wartości, danemu argumentowi nadana zostanie wartość undefined.

function test(argument1, argument2) {
    return argument1 / argument2;
}

test(10, 2); // 5
test(10); // 10 / undefined = NaN

Argumenty

wartości domyślne

JS w wersji ES5 nie pozwala na definiowanie wartości domyślnych dla zmiennych - w zamian tego można nadać wartości domyślne zmiennym w ciele funkcji.

function test(argument1, argument2) {
    argument1 = argument1 || 0;
    argument2 = argument2 || 0;
    return argument1 / argument2;
}

test(10, 2); // 5
test(10); // 10 / 0 = Infinity

Argumenty

nieokreślona ilość argumentów

Jeżeli nie chcemy określać ile argumentów może przyjąć nasza funkcja, możemy skorzystać z niejasnego obiektu arguments, który dostępny jest w ciele funkcji i zawiera tablicę wartości, z jakimi została ona wywołana.

function sum() {
  var ret = 0;
  for(var i = 0 ; i < arguments.length ; i++) {
    ret += arguments[i];
  }
  return ret;
}

sum(1, 2, 3, 4); // 10

Argumenty

funkcje jako argumenty (callback)

W języku JS funkcje traktowane są jako tzw. "obiekty pierwszej klasy" - oznacza to, że mogą być one przekazywane jako argumenty do innych funkcji. 

Funkcje te (przekazane jako argument) nazywamy "zwrotnymi" lub "callbackami":

function test() {
    return 'to jest funkcja test()';
}

function test2() {
    return 'a to jest funkcja test2()';
}

function callMeMaybe(func) {
    return func.call();
}

callMeMaybe(test); // 'to jest funkcja test()'
callMeMaybe(test2); // 'a to jest funkcja test2()'

Ćwiczenie

powitanie

Zasięg

Zasięg

Fragment programu w którym widoczne jest, lub do którego dostęp posiada dane wyrażenie lub polecenie.

Zasięg domyślny

W ES5 istnieje tylko jeden specyfikator zmiennych - var - użycie go deklaruje funkcję w jednym z dwóch "zasięgów":

  • jeżeli użyjemy go w funkcji, zmienna ta będzie widoczna w tej funkcji i wszystkich jej blokach
  • jeżeli użyjemy go poza funkcją, zmienna będzie miała zasięg globalny
var age = 34;
function whatsMyAge() {
    console.log(age);
}
whatsMyAge(); // 34

function whatsMyName() {
    var name = 'Bartosz';
}
whatsMyName();
console.log(name); // ReferenceError

Zasięg funkcji - wyjątek

Wyjątkiem od poprzedniej reguły jest definicja zmiennej w funkcji i przy pominięciu specyfikatora var:

function test() {
    name = 'Bartosz';
}
test();
console.log(name); // 

W takim wypadku zmienna nie zostanie zadeklarowana w zasięgu funkcji, ale zamiast tego zostanie zadeklarowana globalnie - jako window.name

UWAGA: działanie to zmienia się w 'strict-mode'

"Bartosz"​

Zasięg funkcji

IIFE

Czasami mamy potrzebę wykonać jakieś obliczenia, dla których pomocne było by zadeklarowanie kilku zmiennych, ale nie chcemy "zaśmiecać" globalnego zakresu, ani nie chcemy też definiować funkcji. W takim wypadku możemy użyć tzw.  IIFE (immediately-invoked function expression - natychmiastowo wywołane wyrażenie funkcyjne).

var age = (function() {
    var rokUrodzenia = 1984;
    var rok = (new Date()).getFullYear();
    return rok - rokUrodzenia;
})();
console.log(age);

Zasięg funkcji

shadowing

Ponieważ JS nie posiada specyfikatorów zakresu możliwe jest tzw. przesłonięcie ("shadowing") oraz ich ponowna deklaracja:

var person = {
    name: 'Bartosz'
};

function sayHello() {
    var person = 5;
    return person.name;
}

sayHello(); // undefined

function sayHello(person) {
    return person.name;
}

sayHello(); // TypeError

Zasięg funkcji

closure

Każda funkcja utrzymuje w swoim zasięgu referencje do wszystkich zmiennych, do których miała dostęp w momencie jej deklarowania:

function uniqueIdFactory() {
  var id = 0;
  return function() {
    id++;
    return id;
  }
}

var next = uniqueIdFactory();
next(); // 1;
next(); // 2
next(); // 3

Zasięg funkcji

Zaawansowane wykorzystanie closure

for(var i = 0 ; i < 5 ; i++) {
    setTimeout(function() {
	console.log(i);
    }, 100);   
}

Fakt, że closure przetrzymują referencje, może być czasem nieprzydatny, wyobraźmy sobie poniższy przykład:

Powyższy kod wywoła 5 razy funkcję "setTimeout", która po upływie 100 ms wywoła funkcję, która z kolei wyświetli wartość zmiennej i.
Jakie kolejne wartości pokaże kod?

5, 5, 5, 5, 5

Zasięg funkcji

Zaawansowane wykorzystanie closure

for(var i = 0 ; i < 5 ; i++) {
    (function() {
        var j = i;
        setTimeout(function() {
	    console.log(j);
        }, 100);   
    }())
}

W celu zmiany tego zachowania możemy połączyć closure i IIFE:

Każda pętle utworzy swój własny anonimowy zakres, w którym utworzy swoją własną kopię zmiennej i jako j i wyświetli ją po 100 ms za pomocą mechanizmu closure.

0, 1, 2, 3, 4

Ćwiczenie

licznik

Hoisting

Hoisting

"Przeniesienie do góry zasięgu"

Hoisting

Hoisting to mechanizm pozwalający na wywołanie funkcji lub zmiennej zanim zostanie ona zdefiniowana, bez otrzymania komunikatu TypeError

JS wyróżnia dwa oddzielne typy hositowania - dla zmiennych i dla funkcji.

Hoistowanie

Funkcji

Hoistowanie funkcji przenosi jej definicje i deklarację do początku zasięgu, dzięki czemu możemy bez problemu ja wywołać:

var name = 'Bartek';
powitaj(name); // 'Witaj, Bartek'

function powitaj(kogo) {
    console.log('Witaj, ' + kogo);
}

Hoistowanie

Zmiennej

Hoistowanie zmiennej przenosi jej deklarację ale nie definicję. Na skutek tego nie otrzymamy błędu parsera, ale nie będziemy mieć dostępu do wartości zmiennej:

powitaj(name); // 'Witaj, '

var name = 'Bartek';
function powitaj(kogo) {
    console.log('Witaj, ' + kogo);
}

Hoistowanie

Funkcji jako zmiennej

Funkcje zdefiniowane bezpośrednio w wyrażeniu nie są hoistowane!

var name = 'Bartek';
powitaj(name); 

var powitaj = function(kogo) {
    console.log('Witaj, ' + kogo);
}

// TypeError - "powitaj" nie jest funkcją! (jest undefined)

Więcej o: Tablice

Sposoby tworzenia tablic

var array0 = [1, 2, 3, 4];

var array1 = new Array(1, 2, 3, 4);

var array2 = '1234'.split('');

JS oferuje kilka sposobów tworzenia tablic, z czego najpopularniejsze to tzw. literał albo użycie konstruktora obiektu Array.

Niektóre obiekty mogą także definiować metody, pozwalające na przekształcenie ich do tablicy.

Dostęp do danych w tablicy

var array0 = [1, 2, 3, 4];

array[1]; // "2"

array[5]; // "undefined"

Dostęp do danych w tablicy odbywa się przez indeks elementu umieszczony w nawiasach kwadratowych.

Indeksy tablicy numerowane są od zera!

Metody i właściwości obiektu Array

Główny obiekt Array definiuje wiele metod i właściwości pozwalających na pracę z tablicami, kilka wartych uwagi to:

var array = [0, 1, 2];
array.length; // 3

var array2 = array.slice(0, 2); // [0, 1];
var array3 = array2.concat(1); // [0, 1, 1];
var array4 = array3.concat([8, 9]); // [0, 1, 1, 8, 9];
array4.push('a'); // [0, 1, 1, 8, 9, 'a']
array4.indexOf(8); // 3
array4.join('-'); // '0-1-1-8-9-a'
Array.isArray(array4); // true

Dodatkowo dostępne jest także wiele metod przydatnych w programowaniu funkcyjnym (FP) tj.: map, forEach, reduce, sort

Iterowanie po tablicy

W poprzednich rozdziałach używaliśmy pętli for lub while w celu wylistowania wartości z tablicy. Częściej wykorzystywanym mechanizmem jest iterowanie z użyciem przygotowanych przez Array metod:

[1, 2, 3, 4].forEach(function(value, index) {
    console.log('Na pozycji ' + index + ' znajduje się wartość ' + value);
});

var kwadraty = [1, 2, 3, 4].map(function(value) {
  return value * value;
});
kwadraty; // [1, 4, 9, 16]

Usuwanie elementów z tablicy

Istnieje kilka mechanizmów usuwania elementów z tablicy, który z nich stosujemy zależ od tego czy posiadamy indeks elementu oraz czy nie przeszkadza nam, że w tablicy mogą powstać dziury:

var array = [1, 2, 3];
delete array[1];
array; // [1, ,3];

var array = [1, 2, 3];
array = array.filter(function(item, index) {
    if (index === 1) return false;
    return true;
})
array; // [1, 3]

Ćwiczenia

średnia,
unikaty,
takie same

bonus: obliczenie kosztu posiłku

Więcej o: Obiekty

Tworzenie obiektów

literały

Podobnie jak w przypadku tablic istnieje kilka sposobów na tworzenie obiektów. Najczęściej spotykany to literał:

var person = {
    name: 'Bartosz',
    age: 34,
    social: [{
        name: 'Twitter',
        url: 'https://twitter.com/btmpl'
    }, {
        name: 'Medium',
        url: 'https://medium.com/@baphemot
    }]
};

Tworzenie obiektów

operator new

Inną opcją tworzenia obiektów jest wywołanie operatora new oraz funkcji:

function Person() {
    this.name = 'Bartosz';
    this.age = 34;
}

var me = new Person();
me.name; // 'Bartosz'

Ciało takiej funkcji często nazywa się konstruktorem.

Tworzenie obiektów

właściwości (pola) i metody

Obiekty mogą mieć dowolną ilość właściwości / pól (są to "zmienne obiektu") oraz metod ("funkcje obiektu"). Mogą być one zdefiniowane przy definicji obiektu oraz dodane później:

var person = {
    name: 'Bartosz',
    age: 34,
    getMood: function() {
        return 'Nervous';
    }
}

person.getMood(); // 'Nervous';

person.getMood = function() {
    return 'OK';
}

person.getMood(); // 'OK'

Tworzenie obiektów

dostęp do danych obiektu

Aby uzyskać dostęp do danych obiektu stosujemy notację nazwaObiektu.nazwaWłasności:

var person = {
    name: 'Bartosz',
}

person.name; // 'Bartosz';

Wynika z tego, że nazwy własności mają takie same ograniczenia, jak nazwy zmiennych ... ?

Tworzenie obiektów

dostęp "tablicowy"

Można użyć tzw. dostępu tablicowego, by obejść to "ograniczenie":

var person = {
    name: 'Bartosz',
}

person['🥤'] = 'Pepsi Max';
person['Miejsce Zamieszkania'] = 'Łódź';

person;
/*
{
    name: 'Bartosz',
    🥤: 'Pepsi Max',
    Miejsce Zamieszkania: 'Łódź'
}
*/

Ćwiczenie

imię babci

Tworzenie obiektów

dostęp obiektu do samego siebie

Czasami potrzebujemy by obiekty miały możliwość wejrzenia "w siebie" i zwrócenia jakiś danych. W tym przypadku używamy słowa kluczowego this (więcej o this w dalszych rozdziałach).

var person = {
    name: 'Bartosz',
    getName: function() {
        return this.name
    }
}

person.getName(); // 'Bartosz'

Tworzenie obiektów

dostęp obiektu do samego siebie

var person = {
    name: 'Bartosz',
    lastName: 'Szczeciński',
    fullName: this.name + ' ' + this.lastName,
    getName: function() {
        return this.name
    }
};

person.fullName; // ?

this "znajdowane" jest i przypisywane w momencie wywołania wyrażenia, dlatego powyższy zapis jest niepoprawny. Object Literal nie może używać this bezpośrednio w momencie swojej deklaracji.

W takim wypadku this będzie wskazywał na window lub this funkcji nadrzędnej.

Tworzenie obiektów

dostęp obiektu do samego siebie

function personFactory() {
    this.name = 'Bartosz',
    this.lastName = 'Szczeciński',
    this.fullName = this.name + ' ' + this.lastName,
    this.getName = function() {
        return this.name
    }
};

var person = new personFactory();
person.fullName; // 'Bartosz Szczeciński'

Tego typu dostęp do this możliwy jest natomiast w przypadku funkcji, ponieważ this jest dla nas predefiniowane.

Ćwiczenie

fix this

Dziedziczenie

w językach opartych o klasy

class Dog {

    legs = 4;
    
    bark() {
        return 'Woof!'
    }
}

class ChowChow extends Dog {
   
    bark() {
        return '';
    }
}

class Shibe extends Dog {

    legs = 4;
}
var dog = new Dog();
dog.bark(); // "Woof!"
dog.legs; // 4

var dog = new ChowChow();
dog.bark(); // ""
dog.legs; // 4

var dog = new Shibe();
dog.bark(); // "Woof!"
dog.legs; // 4

Dziedziczenie

w JS

JavaScript nie posiada klas w rozumieniu C++ czy Java. Pozwala on jednak na tworzenie obiektów z funkcji za pomocą operatora new i oferuje tzw. "dziedziczenie prototypowe".

 

Każdy obiekt posiada wartość prototype opisującą "szablon" czy "dzielone dane" obiektu.

Dziedziczenie

dziedziczenie prototypowe

function Dog(name) {
    this.name = name;
}

Dog.prototype.bark = function() {
    return 'Woof!';
}

Dog.prototype.getName = function() {
    return this.name;
}

var szarik = new Dog('Szarik');
szarik.bark(); // "Woof!"
szarik.getName(); // "Szarik"

Dziedziczenie

dziedziczenie prototypowe

function Dog(name) {
    this.name = name;
}

var szarik = new Dog('Szarik');

szarik.__proto__.bark = function() {
    return 'Woof!';
}
szarik.bark(); // "Woof!"

var azor = new Dog("Azor");
azor.bark(); // "Woof!"

Dostęp do prototypu funkcji możliwy jest przez zapis nazwaFunkcji.prototype

Jeżeli dysponujemy zaś obiektem (instancją funkcji) możemy użyć niejawnego pola obiekt.__proto__

Dziedziczenie

dziedziczenie prototypowe

function Dog() {

}

Dog.prototype.bark = function() {
    return 'Woof!';
}
Dog.prototype.legs = 4;


function Shiba() {

}
Shiba.prototype = Object.create(Dog.prototype);

W celu zaimplementowania dziedziczenia w JS, musimy zatem sprawić, że różne typy obiektów będą posiadały ten sam prototyp.

Dziedziczenie

dziedziczenie prototypowe

  • każdy obiekt w JS musi posiadać dokładnie jeden prototyp
  • każdy prototyp może posiadać swój prototyp
  • łańcuch prototypów musi być skończony
  • na końcu łańcucha znajduje się null
var test = function() {

}
typeof test; // "function"
test.__proto__; // f() { [native code] }

typeof test.__proto__.__proto__; // "object"
test.__proto__.__proto__; // { constructor: f, ... }

typeof test.__proto__.__proto__.__proto__; // null

Dziedziczenie

dziedziczenie prototypowe

  • oznacza to, że metody prototypowe Object dostępne są dla wszystkich typów obiektów w JS
  • jeżeli zdefiniujemy własność Object.prototype.mojaFunkcja będzie ona dostępna retroaktywnie dla wszystkich obiektów
function test() {

}

var obj1 = new test();

obj1.mojaFunkcja(); // TypeError

Object.prototype.mojaFunkcja = function() {
    return 'Hej!';
}

obj1.mojaFunkcja(); // "Hej!";

Dziedziczenie

dziedziczenie prototypowe

Właściwości obiektów przeszukiwane są w górę łańcucha prototypów, poczynając od instancji.

Object.prototype.mojaFunkcja = function() {
    return 'Jestem w prototypie Object';
}


function test() {

}

test.prototype.mojaFunkcja = function() {
    return 'Jestem w prototypie test';
}

var obj1 = new test();

obj1.mojaFunkcja = function() {
    return 'Jestem w obiekcie test';
}

obj1.mojaFunkcja();

Dziedziczenie

ryzyko definiowania metod prototypowych

Z uwagi na możliwość definiowania włąsnych funkcji prototypowych istnieje ryzyko zdefiniowania funkcji, która zostanie dodan w przyszłej wersji języka i jej nadpisania.

Sytuacja taka nie jest bez precedensu. Popularna biblioteka MooTools definiuje własne implementacje metod Array.flatten i innych, które są niekompatybilne z wytycznymi ECMA Script

 

https://github.com/tc39/proposal-flatMap/pull/56

Dziedziczenie

weryfikacje instancji i metod

Jeżeli potrzebujemy sprawdzić, czy dany obiekt jest instancją danej funkcji, lub czy posiada on własną implementację danej metody możemy użyć operatora instanceof oraz metody prototypowej hasOwnProperty

function Dog() {
    this.bark = function() {
        return 'Woof!';
    }
}

Dog.prototype.wiggleTail = function() {
    this.goodBoy = true;
}

var szarik = new Dog();

szarik instanceof Dog; // true

szarik.hasOwnProperty('bark'); // true
szarik.hasOwnProperty('wiggleTail'); // false
szarik.__proto__.hasOwnProperty('wiggleTail'); // true

Dziedziczenie

Dziedziczenie po wielu klasach

JavaScript umożliwia także dziedziczenie po wielu klasach.

 

Domyślnym mechanizmem jest wciąż dziedziczenie prototypowe na zasadzie:

 

A dziediczy po B

B dziedziczy po C

D dziedziczy po C

 

więc D dziedziczy po A

Dziedziczenie

Dziedziczenie po wielu klasach

function Animal() {

}
Animal.prototype.legs = true;

function Dog() {

}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.tail = true;

function Shiba() {

}
Shiba.prototype = Object.create(Dog.prototype);

var dog = new Shiba();

dog.tail; // true
dog.legs; // true

dog instanceof Shiba; // true
dog instanceof Dog; // true
dog instanceof Animal; // true

var animal = new Animal();

animal.legs; // true
animal.tail; // false

Dziedziczenie

Dziedziczenie po wielu klasach

Drugim sposobem jest dziediczenie po wielu klasach poprzez połączenie ich prototypów. Wciąż daje nam to dostęp do wszystkich właściwości klas "nadrzędnych" ale łamie polimorfizm.

Dziedziczenie

Dziedziczenie po wielu klasach

function Animal() {

}
Animal.prototype.legs = true;

function Dog() {

}
Dog.prototype.tail = true;

function Shiba() {

}
Object.assign(Shiba.prototype, Dog.prototype, Animal.prototype);

var dog = new Shiba();

dog.tail; // true
dog.legs; // true

dog instanceof Shiba; // true
dog instanceof Dog; // false
dog instanceof Animal; // false

Ćwiczenie

mój pies

Re-definiowanie, mutowanie
i niemutowalność zmiennych

Re-definiowanie zmiennych

 

JavaScript (ES5) pozwala na wielokrotne definiowanie tej samej zmiennej i jednoczesną zmianę typu danych:

var name = 'Bartek';
var name = 42;
var name = {
    value: 'Bartek'
};

Działanie takie jest jednak niezalecane ponieważ wprowadza one zamieszanie i utrudnia czytanie kodu aplikacji.

Mutowanie

Mutowaniem nazywamy zmianę wartości obiektu lub tablicy bez ponownego przypisania całej zmiennej:

var person = {
    name: 'Bartek'
};

person.name = 'Bartosz';
person.name; // Bartosz

Działanie takie często może być niepożądane ponieważ może prowadzić do trudnych do wychwycenia błędów:

var person = {
    name: 'Bartek'
};

function sayName(ofPerson) {
    console.log(ofPerson.name);
    // DEBUG: czemu to nie działa ?!?!?
    // muszę sprawdzić jutro po pracy (i oczywiście nie usuwamy kodu - nigdy ...)
    ofPerson.name = 'test';
}

sayName(person); // 'Bartek';
person.name; // 'test';

Immutability

zapobieganie mutowaniu

Aby zabezpieczyć się przed takimi sytuacjami możemy użyć funkcji Object.freeze()

var person = {
    name: 'Bartek'
};
Object.freeze(person);

function sayName(ofPerson) {
    console.log(ofPerson.name);
    // DEBUG: czemu to nie działa ?!?!?
    // muszę sprawdzić jutro po pracy (i oczywiście nie usuwamy kodu - nigdy ...)
    ofPerson.name = 'test'; // operacja została "po cichu" zignorowana
}

sayName(person); // 'Bartek';
person.name; // 'Bartek';

Immutability

Object.freeze

Należy mieć na uwadze, że Object.freeze powoduje tzw. "płytką niemutowalność". Właściwości-właściwości obiektu wciąż można zmieniać!

var person = {
    name: 'Bartek',
    data: {
        age: 34
    }
};
Object.freeze(person);

person.name = 'Test';
person.name; // 'Bartek';

person.data.age = 18;
person.data.age; // "18"

This

This

Czym jest this i na co pozwala?

Jak już widzieliśmy wcześniej, każdy obiekt posiada w sobie niejawny obiekt this. Reprezentuje on "ten" obiekt.

 

Zakres globalny również posiada swój "this" - jest nim sam obiekt zakresu globalnego, np. "window".

 

"this" określane jest w momencie wywołania kodu.

var name = 'Bartosz';
var person = {
    name: this.name,
    age: 34,
    getAge: function() {
        return this.age;
    }
}
name; // Bartosz
person.name; // 'Bartosz'

age; // ReferenceError
person.getAge(); // 34;

This

Jak wskazać wartość "this"

Domyślnie, wartość "this" jest "obiektem znajdującym się po lewo od kropki".

var person = {
    name: 'Bartosz',
    getName: function() {
        return this.name;
    }
}

person.getName();
function Dog() { this.sound = 'Woof!'; }

function Cat() { this.sound = 'Meow!'; }

function makeSound() { 
    return this.sound; 
}

Dog.prototype.makeSound = makeSound;
Cat.prototype.makeSound = makeSound;
var obj1 = new Dog();
var obj2 = new Cat();

obj1.makeSound(); // "Woof!"
obj2.makeSound(); // "Meow!"

This

Jak wskazać wartość "this"

Jeżeli skopiowaliśmy referencję metody do zmiennej i wywołujemy ją bezpośrednio, this przyjmuje wartość this dla kontekstu wywołania ...

var person = {
    name: 'Bartosz',
    getName: function() {
        return this.name;
    }
}

var getName = person.getName;

getName(); // undefined
window.getName();

This

Jak zmienić wartość "this": call, apply

Czasem zachodzi potrzeba wywołania funkcji, która operuje na "this", ale z innym obiektem podstawionym jako "this". Najczęściej przydatne jest to w przypadku callbacków komunikujących się miedzy obiektami lub kontekstami wykonania kodu.

W tym celu JS udostępnia 2 mechanizmy: call oraz apply. Pozwalają one na wywołanie funkcji ze wskazaną wartością jako "this" i przekazanie argumentów po przecinku (Comma) lub jako tablica (Array).

This

Jak zmienić wartość "this": call, apply

var person = {
    name: 'Bartosz',
    getGreeting: function(typPowitania) {
        return typPowitania + ', ' + this.name + ' - jak się masz?'
    }
};

person.getGreeting('Witaj'); // 'Witaj, Bartosz - jak się masz?'

person.getGreeting.call({
    name: 'Bartek'
}, 'Dzień dobry'); // 'Dzień dobry, Bartek - jak się masz?'

This

wykorzystanie apply przy dziedziczeniu

Jeżeli klasa nadrzędna wykonuje operacje w swoim konstruktorze, utworzenie klasy pochodnej nie wywoła tych operacji - w tym wypadku to developer zmuszony jest ręcznie wywołać konstruktor klasy nadrzędnej.

function Animal(name) {
  this.name = name;
  this.sound = '???';
  this.makeSound = function() {
    return this.name + ': ' + this.sound;
  }
}

function Dog(name) {
  this.sound = 'Woof!';
}
Dog.prototype = Animal.prototype;

var obj1 = new Dog('Szarik');
obj1.makeSound(); // TypeError
function Animal(name) {
  this.name = name;
  this.sound = '???';
  this.makeSound = function() {
    return this.name + ': ' + this.sound;
  }
}

function Dog(name) {
  Animal.call(this, name);
  // Animal.apply(this, arguments);
  this.sound = 'Woof!';
}
Dog.prototype = Animal.prototype;

var obj1 = new Dog('Szarik');
obj1.makeSound(); // 'Szarik: Woof!'

This

Jak utrwalić wartość "this".

Często zachodzi potrzeba wywoływania funkcji z ustalonym this więcej niż jednokrotnie, lub zachodzi podejrzenie, że przekazują funkcję i wywołując ją "później" możemy zgubić oryginalną intencję wartości "this".

 

Pomówmy trochę o "pętli zdarzeń" i "stosie" w JS.

This

Jak zmienić wartość "this": bind

JS udostępnia mechanizm "przywiązywania" (bind) this do funkcji.

function Animal(name) {
  this.name = name;
  this.sound = '???';
  this.makeSound = function() {
    console.log(this.name + ': ' + this.sound);
  }
}
var obj1 = new Animal('test');
obj1.makeSound(); // 'test: ???'
setTimeout(obj1.makeSound, 1000); // 'undefined: undefined'
function Animal(name) {
  this.name = name;
  this.sound = '???';
  this.makeSound = function() {
    console.log(this.name + ': ' + this.sound);
  }
}
var obj1 = new Animal('test');
obj1.makeSound(); // 'test: ???'
setTimeout(obj1.makeSound.bind(obj1), 1000); // 'test: ???'

This

Dodatkowe zastosowania bind

bind używany jest także do tzw częściowej aplikacji funkcji.

function mnoz(a, b) {
    return a * b;
}

mnoz(2, 5); // 10
mnoz(5, 5); // 25

var mnozRazy10 = mnoz.bind(this, 10);

mnozRazy10(2); // 20
mnozRazy10(9); // 90

Ćwiczenie

każdy zna moje imię

Wyrażenia regularne

Niektórzy ludzie, kiedy staną w obliczu problemu pomyślą: "Wiem, użyję wyrażeń regularnych."
Teraz mają już dwa problemy

- Jamie Zawinski

Czym są wyrażenie regularne

wg. Wikipedii

(ang. regular expressions, w skrócie regex lub regexp)
wzorce, które opisują łańcuchy symboli.

Wyrażenie regularne pozwalają na zdefiniowanie wzorca, który zostanie użyty na stringu, dzięki czemu możemy zweryfikować czy spełnia on założenia wzorca ("pasuje do") i dodatkowo uzyskać wszystkie jego elementy pasujące do tzw. "pułapek" we wzorcu.

Wyrażenie regularne najczęściej wykorzystuje się do weryfikacji (czy podany ciąg znaków to telefon, email etc.) oraz do "wyciągnięcia" danych ze stringu.

Przykładowe zastosowania

weryfikacja e-mail

'baphemot@gmail.com'.toLowerCase().match(new RegExp(/([a-z0-9]+)@([a-z]+).([a-z]+)/))
/*
  Array [
       "baphemot@gmail.com", 
        "baphemot", 
        "gmail", 
        "com", 
        index: 0, 
        input: "baphemot@gmail.com", 
        groups: undefined
    ]
*/
	
((?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b
\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9]
(?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]
|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:
[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])

Emaile są jednak trochę bardziej skomplikowane

Error Handling & Debugging

Error handling

Throw i Error

W JS występuje wiele typów błędów, większość z nich nie jest błędami krytycznymi.

Error; // podstawowy typ, po którym dziedziczą wszystkie błędy

new Array(-1); // RangeError

var name = 'Bartosz';
console.log(naem); // ReferenceError

while (true === true) 
    console.log('Wszystko wygląda OK ...');
} // SyntaxError

undefined.toString(); // TypeError

decodeURI('http://example.com/%1/test'); // URIError

Error handling

Throw i Error

Użytkownik może samemu wywołać błąd.


new SyntaxError('W naszym kodzie nie pozwalamy na używanie znaków } !');

Oczywiście możemy tworzyć nowe typy błędów korzystając z OOP.

function CustomError(foo, message, fileName, lineNumber) {
  Error.apply(this, arguments);
  this.cosWlasnego = 42;
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, CustomError);
  }
}

CustomError.prototype = Error.prototype;

Error handling

try / catch / finally

W naszej aplikacji będą występować błędy.

function roznicaWiekuOsob(osoba1, osoba2) {
    if (osoba1.personalData.age > osoba2.personalData.age) 
        return osoba1.personalData.age - osoba2.personalData.age;
    else 
        return osoba2.personalData.age - osoba1.personalData.age;
}

var me = {
    personalData: {
        age: 34
    }
};

var barackObama = {
    personalData: {
        age: 61
    }
}

roznicaWiekuOsob(me, barackObama);
roznicaWiekuOsob(30, 15);

Error handling

try / catch / finally

Możemy albo przygotować się na wszystkie możliwości:

function roznicaWiekuOsob(osoba1, osoba2) {
    if (!osoba1 || !osoba1.personalData) return undefined;
    if (!osoba2 || !osoba2.personalData) return undefined;

    if (osoba1.personalData.age > osoba2.personalData.age) 
        return osoba1.personalData.age - osoba2.personalData.age;
    else 
        return osoba2.personalData.age - osoba1.personalData.age;
}

Error handling

try / catch / finally

try {
    // spróbuj
}
catch (err) {
    // ... złap błąd ...
}
finally {
    // ostatecznie ...
}

Ewentualnie możemy użyć notacji try-catch:

  • block try jest obowiązkowy
  • catch lub finally muszą wystepować (ale może wystąpić tylko jeden lub drugi)
  • złapany błąd można rzucić "wyżej"
  • jeżeli błąd nie zostanie obsłużony przez żaden z zakresów "wyżej", aplikacja obsłuży go domyślnie

Error handling

try / catch / finally

function roznicaWiekuOsob(osoba1, osoba2) {
    try {
        if (osoba1.personalData.age > osoba2.personalData.age) 
            return osoba1.personalData.age - osoba2.personalData.age;
        else 
            return osoba2.personalData.age - osoba1.personalData.age;
    }
    catch (err) {
        return undefined;
    }
}

Error handling

try / catch / finally

function roznicaWiekuOsob(osoba1, osoba2) {
    try {
        if (osoba1.personalData.age > osoba2.personalData.age) 
            return osoba1.personalData.age - osoba2.personalData.age;
        else 
            return osoba2.personalData.age - osoba1.personalData.age;
    }
    catch (err) {
        if (err instanceof TypeError) {
            console.warn('Proszę przekazać obiekty!');
            return
        }
        throw err;
    }
}

Niestety, JS nie pozwala na zdefiniowanie typu błędu do złapania - łapane są wszystkie typy. Możemy użyć detekcji typów by obsłużyć tylko niektóre z nich.

Logowanie

console

Narzędziem "najniższego poziomu" jeżeli chodzi o debugowanie aplikacji jest obiekt console pozwalający na wyświetlanie informacji w konsoli przeglądarki.

(Nie jest to funkcjonalność JS!)

Logowanie

console

var person = {
	name: 'test'
}
console.log(person);
person.name = 'Bartek';

Debuggowanie

stack trace

function fun1() {
	fun2();
}

function fun2() {
	fun3();
}

function fun3() {
	return a + 5;
}

fun1();

W przypadku nieobsłużonego wyjątku (lub gdy wywołany zostanie ręcznie - np. console.trace) zwrócony zostaje tzw. "stack tracwe" czyli lista funkcji, których wywołanie doprowadziło do wystąpienia błędu:

Debuggowanie

breakpoint / debugger

JS dostarcza nam słowo kluczowe debugger, wywołanie którego spowoduje wywołanie debuggera aktualnie używanego silnika (np. w przeglądarce):

Wiele przeglądarek oferuje także opcję "break on exception"

Strict Mode

Strict mode

Czemu?

JS zawiera wiele "błędów" i "dziwactw".

 

Gdyby autorzy chcieli je usunąć, mogli by popsuć kod, który na nich polega.

 

W celu "ulepszenia" JS wprowadzono tzw. "strict mode", który zmienia działanie niektórych funkcji języka i w ES5 działa on w trybie "opt-in"

Strict mode

Jak aktywować

'use strict'
var name = 'Bartosz';
age = 34;
function test() {
    'use strict'
    console.log(this);
}

test();

dla całego pliku

dla pojedynczej funkcji

Strict mode

(Najważniejsze) różnice

  • nowe słowa zastrzeżone: "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield"
  • każda zmienna musi być zadeklarowana
'use strict'
name = 'Bartosz'; // ReferenceError
name = 'Bartosz'; // window.name
name; // "Bartosz"
  • zabronione jest definiowanie funkcji w funkcjach
  • zabronione jest modyfikowanie this w funkcjach nie będących metodami lub konstruktorami
010; // 8
'use strict'
010; // SyntaxError
  • wartości w systemie ósemkowym są niedozwolone

function Person(name) {
  this.name = name;
}
Person('test'); // OK!
var obj = new Person('test'); // OK!
'use strict'
function Person(name) {
  this.name = name;
}
Person('test'); // TypeError
var obj = new Person('test'); // OK!

Web APIs

DOM

Jednym z najważniejszych Web API jakie udostępniają przeglądarki jest DOM - Document Object Model - pozwalające na bezpośrednią interakcję (odczytywanie i modyfikowanie) drzewiastej struktury dokumentu HTML.

 

To dzięki temu możemy tworzyć interaktywne strony internetowe, oraz korzystać z bibliotek i frameworków jak React, Angular etc.

DOM

<!doctype html>
<html>
    <head>
        <title>Moja strona internetowa</title>
        <meta charset="utf-8" />
    </head>
    <body>
        <h1>
            Witam na mojej stronie internetowej!
        </h1>
        <p>
            Znajdziesz tutaj wiele informacji na tematy takie jak:
        </p>
        <ul>
            <li>informatyka</li>
            <li>filmy</li>
            <li>muzyka</li>
            <li>i wiele innych!</li>
        </ul>
        <p id="link"><a href="#">więcej o mnie</a></p>
        <form action="?" method="post">
            <input type="text" name="email" value="Twój adres" />
            <input type="submit" value="Zapisz się na newsletter" />
        </form>
        <script src="./script.js"></script>
    </body>
</html>

DOM

Interakcja ze strukturą dokumentu

Głównym interfejsem do DOM jest obiekt document, który udostępnia wiele metod pozwalających na interakcję ze strukturą dokumentu - zarówno odczytywanie jak
i modyfikację.

 

Każde zapytanie wyszukiwania powinno rozpocząć się od document (lub od już wcześniej uzyskanego elementu).

 

Domyślnie otrzymujemy 3 "skrótowe" elementy:

document.documentElement; // <html>
document.head; // <head>
dobument.body; // <body>

DOM

odczytywanie struktury

Najczęściej stosowana operacja to odczytywanie struktury.
W tym celu obiekt document udostępnia nam kilka metod pozwalających na wyszukanie elementów w oparciu o różne kryteria:

 

document.getElementsByTagName

document.getElementById

document.getElementsByClassName

document.querySelector / querySelectorAll

DOM

odczytywanie struktury

// "skrócony" dostęp do elementu <head>
document.head;

// odnajdź element o atrybucie id z wartością "link"
document.getElementById('link');

// znajdź *pierwszy* element <a> osadzony w elemencie <p>
document.querySelector('p a');

// znajdź wszystkie elementy <p> i zwróć ich kolekcję (prawie Array!)
document.querySelectorAll('p')

// odnajdź elementy <a> osadzone w elemencie o id="link"
document.getElementById('link').querySelectorAll('a');

DOM

relacje między węzłami

Mając już odnaleziony węzeł, możemy przemierzać drzewo relatywnie wzg. naszego węzła:

var link = document.getElementById('link');


link.parentNode; // <body>
link.children[0]; // <a href="#">więcej o mnie</a>
link.firstChild; // <a href="#">więcej o mnie</a> 

document.querySelector('ul').firstElementChild; // <li>informatyka</li>

document.querySelectorAll('li')[1].nextElementSibling; // <li>muzyka</li>

DOM

relacje między węzłami

DOM

różnica między node a element

#text
  • wszystkie elementy są nodami (element dziedziczy po node)
  • nie wszystkie nody są elementami

DOM

interakcja z elementem - odczytywanie danych

Po uzyskaniu dostępu do znacznika możemy (w zależności od jego typu) wchodzić w interakcję z jego atrybutami i metodami.

W celu weryfikacji typu węzła możemy użyć
atrybutu .nodeType i .nodeName

var p = document.querySelector('p#link');
p.nodeName; // "P"

p.nodeType; // "1"

/*
    1 = element HTML - <p>
    3 = tekst - #text
    4 = komentarz - <!-- komentarz -->
    9 = dokument - #document
    10 = dokument definicji - <!doctype html>
*/

DOM

interakcja z elementem - odczytywanie danych

Każdy znacznik HTML posiada własną, unikalną i rozbudowaną reprezentację w postaci obiektu danej klasy:

var input = document.querySelector('input');
input; // <input type="text" name="email" value="Twój adres" />

input.constructor.name; // "HTMLInputElement"

input.value; // "Twój adres"
input.getAttribute('value'); // "Twój adres"
var p = document.querySelector('p#link');
p; // <p id="link"><a href="#">więcej o mnie</a></p>

p.constructor.name; // "HTMLParagraphElement"

p.innerHTML; // '<a href="#">więcej o mnie</a>'

DOM

interakcja z elementem - zapisywanie danych

Większość atrybutów można zmienić bezpośrednio poprzez interakcję z węzłem:

var input = document.querySelector('input');
input; // <input type="text" name="email" value="Twój adres" />

input.value; // "Twój adres"

input.value = 'Proszę, podaj swój adres e-mail';

input.value; // "Proszę, podaj swój adres e-mail";

Niektóre można zmienić także lub tylko używając wyspecjalizowanych metod:

var input = document.querySelector('input');
input; // <input type="text" name="email" value="Twój adres" />

input.value; // "Twój adres"

input.setAttribute('value', 'Proszę, podaj swój adres e-mail');

input.value; // "Proszę, podaj swój adres e-mail";

Ćwiczenie

Link "więcej o mnie" nie prowadzi nigdzie. Zmień go używając JS tak, by prowadził on do Twojej strony (FB, Linkedin, blog etc.)

DOM

tworzenie nowych węzłów

DOM pozwala na zmianę zawartości węzła używając .innerHTML:

var element = document.querySelector('ul');
element.innerHTML = element.innerHTML + '<li>test</li>'

Rozwiązanie takie jednak nie powinno być używane do dynamicznego tworzenia węzłów. Przydaje się ono np. w przypadku, kiedy mamy fragment HTML jako string (np. z API) i musimy dodać go do strony, ale nie zamierzamy go parsować i ręcznie budować struktury.

Ćwiczenie

używając JS, dodaj kilka tematów do listy

DOM

document.createElement

Jeżeli mamy listę informacji, które chcemy dodać do strony (np. tablica obiektów) a nie string, powinniśmy użyć document.createElement

var li = document.createElement('li');
li.innerText = 'nowe informacje już niebawem!';

document.querySelector('ul').appendChild(li);
document.createElement;
document.createTextNode;
document.createComment;
element.appendChild
element.insertBefore(otherChild);
element.replaceChild(otherChild);

element.removeChild(childToRemove);
element.remove();

Ćwiczenie

Używając JS, dodaj do listy kilka tematów, ale niech
"i wiele innych!" będzie na końcu.

Nie używaj .innerHTML - usuń tamtą implementację :)

DOM

manipulowanie klasami CSS

Klasy CSS można modyfikować używając setAttribute/getAttribute, ale ponieważ operacja ta jest bardzo częsta, dysponujemy obiektem .classList który ułatwia te modyfikację:

var el = document.querySelector('.main'); // <li class="main">informatyka</a>

el.classList.remove('main');
el.nextElementSibling.classList.add('main');

.add

.remove

.toggle

.contains

Window

O window mówiliśmy już w sekcji o ES5 i ustaliliśmy, że jest to obiekt globalnego zasięgu, w którym przechowywane są różne zmienne.

 

window jest obiektem definiowanym przez przeglądarkę i poza przetrzymywaniem zmiennych udostępnia on nam dostęp do wielu funkcji okna przeglądarki tj. nawigacja, otwieranie i zamykanie kart, komunikacja z użytkownikiem etc. oraz do wielu dodatkowych Web API.

 

Jako zakres globalny window posiada też referencję do siebie samego.

window.window

window.window.window

window.window.window.window

window.window.window.window.

Window

Komunikacja z użytkownikiem

Podstawowe metody komunikacji z użytkownikiem to alert, prompt i confirm.

alert('Witaj na mojej stronie!');

var imie = prompt('Podaj swoje imię', 'Wartość domyślna');

var wynik = confirm('Czy na pewno chcesz usunąć element?');

Ćwiczenie

Po wejściu na stronę zapytaj użytkownika o jego imię i wyświetl je na stronie w formie "Witaj, $IMIE na mojej stronie internetowej!".

Jeżeli użytkownik nie podał imienia, pokaż "Witaj na mojej stronie internetowej!" (bez przecinka).

Window

Manipulowanie adresem strony

Obiekt window.location pozwala nam na odczytanie i kontrolowanie "adresu URL" z danego okna:

window.location; // { replace: f, assign: f, href: "file:///Users/Bartek ...."

window.location.reload();
window.location.href = window.location.href;

window.location.href = 'https://www.google.com';

window.location.hash = '#Fragment';

Window

Zarządzanie oknem

JavaScript może otworzyć nowe okno / zakładkę, oraz może zamknąć okno które sam otworzył.

var win = window.open('http://example.com');
win.close();

Funkcja .open przyjmuje 3 parametry:

  • adres URL strony, która ma zostać otworzona
  • nazwa okna, pod jaką zostanie otworzone (będzie dostępne w nim jako window.name :))
  • konfiguracja okna jako string (resizable=yes,scrollbars=yes,status=yes,width=500,height=300)

Window

Zarządzanie oknem

window udostępnia nam też wiele danych o rozmiarze i położeniu okna, oraz pozwala nimi manipulować.

window.innerHeight - wysokość przeznaczona dla zawartości

window.innerWidth - szerokość

window.scrollTo(x, y) - pozwala na przewinięcie strony

window.print() - wyświetl interfejs drukowania strony

window.postMessage() - pozwala na komunikowanie się pomiędzy stronami / oknami w ramach jednej sesji przeglądarki

Window

Zdarzenia odłożone w czasie

Innym istotnym API jest setTimeout i setInterval pozwalające nam na zdefiniowanie funkcji, która zostanie wywołana "później" lub "co X milisekund"

function przypomnijSie() {
    alert('Hej, jesteś na mojej stronie już 5 sekund!');
}

setTimeout(przypomnijSie, 5000);
function przypomnijSie() {
    alert('Hej, jesteś na mojej stronie już 5 sekund!');
}

var timeoutId = setTimeout(przypomnijSie, 5000);
clearTiemout(timeoutId);

Dobrym zwyczajem jest po sobie "posprzątać" i usunąć wszelkie timeouty, które jeszcze się nie wywołały:

Window

Zdarzenia powtarzające się

setInterval pozwala nam na zdefiniowanie zadań, które wywołają się co określony interwał czasowy.

Możemy dzięki temu cyklicznie aktualizować UI lub sprawdzać, czy użytkownik wykonał już daną operację.

var countdown = 10;
var intervalId = setInterval(bomba, 1000);

function bomba() {
    countdown--;
    if (countdown === 0) {
        alert('BOOM!');
        clearInterval(intervalId);
    }
    console.log(countdown + ' ...');
}

Ćwiczenie

Dodaj na stronie zegarek, wskazujący aktualną godzinę, minuty i sekundy. Zegarek powinien aktualizować się co sekundę.

 

Dane możesz pobrać z obiektu Date; w jaki sposób będziesz je aktualizował zależy od Ciebie.

 

Zegar powinien mieć formę:
HH:MM:SS

Magazyn Danych

Przeglądarki standardowo udostępniają dwa magazyny danych:

 

localStorage pozwalający na przechowywanie wartości tekstowych aż do czasu ich usunięcia przez użytkownika

sessionStorage podobnie jak localStorage, jednak wartości przechowywane są do końca sesji przeglądarki.

 

Oba dziedziczą po interfejsie Storage.

Magazyn Danych

Obiekty typu storage dysponują prostym api:

 

Storage.length - zwraca ilość obiektów aktualnie przechowywanych

Storage.setItem(key, value) - przypisuje string (value) do danego klucza

Storage.getItem(key) - zwraca string przypisany do danego klucza

Storage.removeItem(key, value) - usuwa wpis o podanym kluczu

Storage.clear() - czyści storage

Storage.key(index) - zwraca nazwę n-tego klucza obiektu

Magazyn Danych

localStorage.setItem('name', 'Bartek');

var person = {
    name: 'Barosz',
    lastName: 'Szczeciński'
};

localStorage.setItem('person', person); // 

localStorage.setItem('person', JSON.stringify(person));

var otherPerson = JSON.parse(localStorage.getItem('person'));
Nie ma błędu - "[object Object]"

Magazyn Danych

pobieranie wszystkich danych

for(var i = 0 ; i < localStorage.length ; i++) {
    var key = localStorage.key(i);
    var value = localStorage.getItem(key);
    console.log(value);
}

// albo

var keys = Object.keys(localStorage);
keys.forEach(function(key) {
    var value = localStorage.getItem(key);
    console.log(value);
});

Ćwiczenie

Nasza strona pyta użytkownika za każdym razem o imię (chyba, że już to was denerwowało i wyłączyliście ;)).

Zadbajmy o to, żeby nasza strona pytała użytkownika o imię tylko raz.

Zdarzenia

Zdarzenia (Events) to czynności, które użytkownik wykonuje podczas interakcji ze stroną, które "emitowane" są przez poszczególne jej elementy i mogą zostać "obsłużone" przez JS.

 

Zdarzenia opisują także mechanizmy zachodzące w samej przeglądarce (zmiana stanu dokumentu, zakończenie pobierania zasobów, komunikat z innej strony etc.)

 

Każdy "handler" wywoływany jest z obiektem danego zdarzenia, zawierającym jego meta-informacje.

Zdarzenia

dodawanie zdarzeń

Z historycznych względów możliwe jest dodanie handlera do elementu używając atrybutu on*eventName* i zapisanie w nim kodu, jaki powinien zostać wykonany:

<a href="#" onClick="alert('Hej!')">

Ten sposób jest ograniczony i wstawianie większego kodu wiąże się z koniecznością tworzenia wielu "zbędnych" funkcji. Również modyfikowanie tak dodanych zdarzeń jest utrudnione.

Zdarzenia

dodawanie zdarzeń

Ewolucją poprzedniego kodu jest poniższa notacja, niestety ona również jest ograniczona pod niektórymi względami:

function hej() {
    alert('Heeej!');
}

document.querySelector('p#link a').onclick = hej;

Zdarzenia

interakcja między sposobami przypisania

Użycie właściwości onclick na elemencie, który miał już dodany atrybut onclick w dowolny inny sposób usuwa poprzednio dopisany handler:

<a href="#" onClick="alert('Witaj!')">

// ...

function hej() {
    alert('Heeej!');
}

document.querySelector('p#link a').onclick = hej;

Po kliknięciu linku wyświetli się tyko komunikat "Heeej!"

Zdarzenia

dodawanie zdarzeń

"Poprawnym" sposobem dodani obsługi zdarzenia jest użycie na elemencie metody addEventHandler, podać typ zdarzenia jakie chcemy obsłużyć oraz przekazać referencję na funkcje, która powinna zostać wywołana:

document.querySelector('p#link a').addEventListener('click', function() {
    alert('Papa!');
});
function pozegnaj() {
    alert('Papa!');
}

document.querySelector('p#link a').addEventListener('click', pozegnaj);
function pozegnaj() {
    alert('Papa!');
}

document.querySelector('p#link a').addEventListener('click', pozegnaj()); // ŹLE!

Zdarzenia

usuwanie zdarzeń

W celu usunięcia nasłuchiwania na zdarzenie, musimy użyć metody removeEventListener, podać typ zdarzenia oraz referencję na już dodany "handler":

function pozegnaj() {
    alert('Papa!');
}

document.querySelector('p#link a').addEventListener('click', pozegnaj);
document.querySelector('p#link a').removeEventListener('click', pozegnaj);
document.querySelector('p#link a').addEventListener('click', function() {
    alert('Papa!');
});

// NIE DA SIĘ USUNĄĆ!

Ćwiczenie

Dodaj zdarzenie do guzika wysłania formularza, tak by po kliknięciu pobierana była wartość pola tekstowego.

Następnie zweryfikuj czy w wartości znajduje się znak @.

Jeżeli tak, pokaż podziękowanie.

Jeżeli nie, pokaż komunikat błędu.

Zdarzenia

Parametry funkcji

Każdy "handler" wywoływany jest z obiektem danego zdarzenia, zawierającym jego meta-informacje.

document.querySelector('input[type="text"]').addEventListener('click', function(ev) { 
    console.log(ev)
});

https://developer.mozilla.org/en-US/docs/Web/API

Zdarzenia

Parametry funkcji

Każdy handler wywoływany jest z this które wskazuje na obiekt obsługujący zdarzenie.

var person = {
  getName: function() { console.log(this); }
}
document.querySelector('form').addEventListener('click', person.getName);

Zdarzenia

Parametry funkcji

Aby upewnić się, że this jest "właściwe" i wskazuje na obiekt "person", możemy użyć .bind

var person = {
  getName: function() { console.log(this); }
}
document.querySelector('form').addEventListener('click', person.getName);

Zdarzenia

"bubbling"

function handleClick(event) {
  console.log(event.target, event.currentTarget, event.target === event.currentTarget);
}

document.querySelector('ul li:first-child').addEventListener('click', handleClick);
document.querySelector('ul').addEventListener('click', handleClick);
document.querySelector('body').addEventListener('click', handleClick);

Zdarzenia

"bubbling"

Jeżeli jakikolwiek z rodziców klikniętego elementu również posiada dodany handler dla danego typu elementów, domyślnie zostanie on również wywołany.

 

Zjawisko to nazywane jest "bubblingiem" - kolejność wywołania handlerów jest "do góry" od faktycznie klikniętego elementu.

 

Obiekt event posiada atrybut target
i currenTarget pozwalający nam określić, czy wywołany handler dotyczy zdarzenia aktualnie obsługiwanego elementu.

Zdarzenia

"bubbling"

Zachowanie to może zostać powstrzymane przez wywołanie metody stopPropagation na obiekcie event.

function handleClick(e) {
  e.stopPropagation()
  console.log(event.target, event.currentTarget, event.target === event.currentTarget);
}

document.querySelector('ul li:first-child').addEventListener('click', handleClick);
document.querySelector('ul').addEventListener('click', handleClick);
document.querySelector('body').addEventListener('click', handleClick);

Zdarzenia

"capture phase"

Na podstawie https://javascript.info/bubbling-and-capturing

Zdarzenia

Powstrzymywanie pozostałych zdarzeń.

Jeżeli na elemencie dodanych jest wiele zdarzeń, wywoływane są one w kolejności w jakiej zostały dodane (i w odpowiedniej fazie). Jeżeli chcemy nie tylko powstrzymać zdarzenie przed propagacją "wyżej" ale także zatrzymać wszystkie inne zdarzenia na danym obiekcie możesz użyć metody
.stopImmediatePropagation

document.querySelector('input[type="submit"]').addEventListener('click', function(event) {
    event.stopImmediatePropagation();
    // ....
});

Zdarzenia

Powstrzymywanie domyślnych zachowań.

Zapewne zauważyłeś, że nawet kiedy podamy błędny email i zobaczymy komunikat błędu, formularz jest i tak wysyłany? Dzieje się tak ponieważ nasz element submit ma też swoje domyślne zachowanie - wysyła formularz w którym jest osadzony. Aby to zmienić używamy metody preventDefault.

document.querySelector('input[type="submit"]').addEventListener('click', function(event) {
    event.preventDefault();
    // ....
});

Zdarzenia

istotne zdarzenia

document

- DOMContentLoaded - wywoływany po wczytaniu całej zawartości strony

element

- MouseEvent: click, dblclick, contextmenu, mouseover, mousemove, wheel

- KeyboardEvent: keydown, keyup, keypress

- TouchEvent: touchstart, touchend, touchcancel, tuochmove

- UIEvent: scroll, load, unload

- FocusEvent: blur, focus (te zdarzenia pomijają bubbling!)

- Event: submit, reset

https://developer.mozilla.org/en-US/docs/Web/Events

AJAX

AJAX nie jest technologią. AJAX to nie biblioteka. AJAX to nie funkcja.

 

AJAX to idea połączenia kilku technologii, umożliwiająca nowe sposoby interakcji ze stronami internetowymi poprzez ulepszona komunikację z serwerem.

 

Asynchronous JavaScript And XML

 

Na szczęście to ostatnie głównie z nazwy.

AJAX

Typowa sesja HTTP

AJAX

Minusy

  • stan interfejsu użytkownika nie jest zachowany, co powoduje utratę wprowadzonych danych, chyba, że przesyłamy je pomiędzy klientem a serwerem
     
  • konieczność przesyłania dodatkowych danych - spowolnienie procesu; konieczność podmiany całej strony
     
  • długi czas oczekiwania na odpowiedź serwera - poczucie braku interaktywności

AJAX

Implementacja

  • większość przeglądarek udostępnia aktualnie dwa interfejsy komunikacji z serwerem dla technologii AJAX: XMLHttpRequest oraz fetch
     
  • nie pokrywają się one w 100% funkcjonalnością, więc oba są jeszcze przydatne
     
  • IE nie obsługuje fetch :(

AJAX

XMLHttpResponse

var xml = new XMLHttpRequest();
xml.open('GET', 'https://jsonplaceholder.typicode.com/posts');
xml.onreadystatechange = function() {
  if(xml.readyState == 4) {
    if(xml.status === 200 || xml.status === 304) {
      console.log(xml.responseText);
    }
    else {
      console.log('Wystąpił błąd: ', xml.status);
    }    
  }
}
xml.send();

AJAX

XMLHttpResponse

.open(metoda, url, async, user, password) - przygotowuje połączenie

.send(content) - wysyła żądanie, z opcjonalnym ciałem (dla POST, PATCH, PUT)

.abort() - zatrzymuje trwające żądanie

.setRequestHeader(header, value) - pozwala na ustawienie nagłówków żądania

.getResponseHeader(header) - pobiera dany nagłówek odpowiedzi

.getAllResponseHeaders() - *string* z wszystkich nagłówków odpowiedzi

AJAX

XMLHttpResponse

onreadystatechange - zdarzenie uruchamiane przy zmianie stanu połączenia

readyState - aktualny stan połączenia

responseText, responseXML - odpowiedź w odpowiednim formacie

status - kod HTTP odpowiedzi

statusText - "czytelny dla człowieka" status odpowiedzi (np. "Not Changed")

AJAX

XMLHttpResponse

function get(url, success, failure) {
    var xml = new XMLHttpRequest();
    xml.open('GET', url);
    xml.onreadystatechange = function() {
      if(xml.readyState == 4) {
        if(xml.status === 200 || xml.status === 304) {
          success.call(null, xml.responseText);
        }
        else {
          failure.call(null, xml.status, 'Wystąpił błąd');
        }    
      }
    }
    xml.send();
}

get('https://jsonplaceholder.typicode.com/posts', function(responseBody) {
    console.log(responseBody);
}, function(errCode, errInfo) {
    console.log(errCode);
});

Ćwiczenie

Pobierz listę postów z adresu http://vps88089.ovh.net:3000/posts i dodaj
5 z nich jako lista uporządkowana do strony blog.html
Utwórz (używając JS) strukturę:

<h4>Na blogu:</h4>
<ul>
    <li>wartość "title" pierwszego wyniku</li>
    <li>wartość "title" drugiego wyniku</li>
    <li>wartość "title" trzeciego wyniku</li>
    <li>wartość "title" czwartego wyniku</li>
    <li>wartość "title" piątego wyniku</li>
</ul>

Ściągawka: document.createElement, element.innerText, element.innerHTML

Ćwiczenie

Po kliknięciu na każdy z elementów utworzonej poprzednio listy:

- przekieruj użytkownika na adres #/post/ID_POSTU

- pobierz szczegóły postu z adresu
http://vps88089.ovh.net:3000/posts/ID_POSTU

- pobierz komentarze do danego postu z adresu http://vps88089.ovh.net:3000/comments?postId=ID_POSTU

- wyświetl szczegóły postu

- wyświetl nicki (wszystko przed @) osób komentujących oraz ich komentarze

Ściągawka: element.addEventListener, window.location.hash, string.split, element.innerHTML

Ćwiczenie

Jeżeli przejdziesz teraz na szczegóły postu na Twoim blogu, i odświeżysz stronę, szczegóły postu nie są załadowane.

Naprawmy ten błąd :)

 

Przy okazji, dodajmy link "wróć do strony głównej" i upewnijmy się, że po przejściu na stronę główną ładowane są posty.

Ściągawka: window.location.hash, obiekt window emituje zdarzenie "hashchange"

(ale nie jest ono wywoływane przy pierwszym ładowaniu strony!)

Ćwiczenie

Dodajmy formularz komentowania na naszej stronie. Formularz powinien mieć pole na adres email oraz na tekst komentarza. Po wypełnieniu (i upewnieniu się, że podano jakąś treść) dane prześlijmy w formacie JSON na adres http://vps88089.ovh.net:3000/comments

Struktura komentarza to: author (string), body (string) i postId (number)

Ściągawka: xml.setRequestHeader('Content-type', 'application/json'), JSON.stringify

DOM

inne, warte uwagi

  • audio - odtwarzanie, nagrywanie, text-to-speach
     
  • grafika - canvas: 2D, 3D, akcelerowana grafika, kamera
     
  • komunikacja - fetch, WebRTC
     
  • geolokacja

Wprowadzenie do JavaScript

By btmpl

Wprowadzenie do JavaScript

Standardzie ECMAScript 5, WebAPI, praca z REST.

  • 663