Upload
others
View
6
Download
0
Embed Size (px)
Citation preview
Język programowania JavaScript
dr hab. inż. Marek Wojciechowski
2
Historia języka JavaScript
• Opracowany przez firmę Netscape w 1995 roku
– Brendan Eich strorzył JavaScript w 10 dni
– Język skryptowy dla przeglądarki Netscape Navigator jako prostsza alternatywa i uzupełnienie dla apletów Java (Robocze nazwy: Mocha, LiveScript)
– Serwerowa implementacja JavaScript dla Netscape Enterprise Server udostępniona krótko po wersji dla przeglądarki
• W 1996 Microsoft udostępnia JScript jako port JavaScript
• Standaryzacja języka pod nazwą ECMAScript (ECMA-262)
• JavaScript, JScript, ActionScript jako implementacje ECMAScript
3
Historia wersji ECMAScript
• 1997 – 1st Edition, 1998 – 2nd Edition (zmiany edycyjne)
• 1999 – 3rd Edition (try/catch, wyrażenia regularne, …)
• 4th Edition – zarzucona (złożoność języka, względy polityczne)
• 2009 – 5th Edition (strict mode, wsparcie JSON)
• ES 2015/6 (klasy, moduły, funkcje strzałkowe, nowe operatory, …)
• ES 2016/7 (**), ES 2017/8 (async/await), ES 2018/9 (drobne uzupełnienia)
4
Cechy języka JavaScript
• Łączy programowanie strukturalne, obiektowe i funkcyjne
• Język skryptowy, interpretowany – Uruchamiany w maszynach wirtualnych nazywanych silnikami JavaScript
– Silniki JavaScript dostępne w przeglądarkach internetowych, ale nie tylko
– Kompilacja just-in-time w niektórych przeglądarkach
• Język dynamiczny – Dynamiczne typowanie (typ związany z wartością, a nie zmienną)
– Dynamiczna zmiana zestawu właściwości i funkcjonalności obiektów
– Definiowanie funkcji w trakcie pracy programu
– Możliwość wykonania kodu dostarczonego jako łańcuch znaków
• Funkcja eval()
• Programowanie obiektowe oparte na prototypach
– Zamiast dziedziczenia opartego na klasach
5
Zastosowania JavaScript
• Przeglądarki WWW (frontend)
– Jedna z 3 podstawowych technologii do tworzenia zawartości stron WWW (obok HTML i CSS)
– Dynamiczne generowanie zawartości po stronie przeglądarki
• Rozwiązania serwerowe dla aplikacji WWW (backend)
– Node.js
• Bazy danych NoSQL
– JSON jako format danych, JavaScript do zapytań i przetwarzania danych (MongoDB, CouchDB)
• Inne zastosowania jako wbudowany język skryptowy
– Rozszerzenia przeglądarek, Adobe Acrobat i Reader, Open Office, …
• Język pośredni dla transkompilatorów nowych języków
– TypeScript, CoffeeScript, Dart, …
6
Zastosowania JS na stronach WWW
• Dawniej: Dynamic HTML (DHTML)
– Logika programowa dodana do HTML-a
– Skrypty dokonują interakcji z obiektowym modelem dokumentu (Document Object Model (DOM)), modyfikacje strony w przeglądarce poprzez interfejs DOM
– Przykłady zastosowań:
• Animacje, gry, odtwarzanie multimediów
• Walidacja danych wprowadzanych do formularzy
• Otwieranie dodatkowych okien
• Obecnie: Frontend development
– Rozwinięcie DHTML
– Logika prezentacji w JavaScript po stronie przeglądarki
– Logika biznesowa w dowolnej technologii po stronie serwera
– Żądania HTTP z poziomu JavaScript wywołujące logikę biznesową (Ajax)
7
JavaScript a Java
• „Java is to Javascript as car is to carpet”
• Podobieństwa i związki:
– Składnia obu języków wywodzi się ze składni języka C
– Standardowa biblioteka JavaScriptu zachowuje konwencje Javy
– Obiekty Math i Date w JavaScript oparte na Math i Date z Java 1.0
– Wszystkie słowa kluczowe języka Java były zastrzeżone w pierwszej wersji JavaScript
• Najważniejsze różnice między JavaScript i Java:
Cecha JavaScript Java
Typowanie Dynamiczne Statyczne
Kompilacja Brak Do bajtkodu
Obiektowość Prototypy Klasy
Programowanie funkcyjne Od początku Lambdy od Java 8
Składnia Specyficzne operatory i instrukcje sterujące
8
Typy danych w JavaScript
• Typy proste
– Liczba (number) – 64-bity, zmiennoprzecinkowe (IEEE 754 standard)
• np. 2, -3, 3.14, 2.9e8, 0xFF, 0b1011, 0o67, Infinity, -Infinity, NaN
– Łańcuchy znaków (string) – niemodyfikowalne
• ograniczniki: " ", ' ', ``; znaki specjalne: \n, \t, \\, \', \""
• np. 'Test stringa\n', "Drugi 'string'."
– Wartości logiczne (boolean): true, false
• Struktury danych
– Tablice, np. [3, 5, -3] (tablice są obiektami)
– Obiekty, np. { marka: 'Opel', nrRej: 'PO65947' };
• Specjalne wartości
– null (typu object, przez pomyłkę…) – brak wartości, brak obiektu
– undefined (typu undefined) – wartość zadeklarowanej, ale nie zainicjalizowanej zmiennej lub właściwości obiektu
– symbole
9
Operatory w JavaScript
• ., [], ()
• Arytmetyczne: +, -, *, /, %, ++, --, ** (ES7)
• Konkatenacja łańcuchów znaków: +, +=
• Bitowe: &, |, ^, ~, <<, >>, >>>
• Przypisania: =, +=, -=, …, <<=, …, &=, …
• Porównania: ==, !=, ===, !==, >, >=, <, <=
• Logiczne: &&, ||, ! (skrócone wartościowanie && i ||)
• new, delete, typeof, void, in, instanceof
• Operator przecinka: ,
• Operator warunkowy (ternarny): ?:
• Destrukturyzacja tablicy/obiektu (ES6)
• Operatory rest i spread: … (ES6)
10
Automatyczna konwersja typów
console.log(5 * null) // 0
console.log(null * 5) // 0
console.log("6" - 1) // 5
console.log(6 - "1") // 5
console.log("6" + 1) //61
console.log(6 + "1") // 61
console.log("three" * 2) // NaN
console.log(2 * "three") // NaN
console.log(3 + null); // 3
console.log(3 + undefined); // NaN
console.log(3 + false); // 3
console.log(true + true); // 2
11
Operatory ==/!= i ===/!==
• ===/!== testują precyzyjną równość
– Oprócz wartości uwzględniają typ danych
console.log(false == 0); // true
console.log(false === 0); // false
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(null == 0); // false
console.log(null === 0); // false
console.log(false != 0); // false
console.log(false !== 0); // true
console.log(null != undefined); // false
console.log(null !== undefined); // true
console.log(null != 0); // true
console.log(null !== 0); // true
12
Pułapki porównań przez ==
• Elastyczność języka JavaScript w zakresie konwersji typów sprawia, że wyniki porównań operatorem == mogą nie być oczywiste
console.log(0 == '0'); // true
console.log(0 == ''); // true
console.log('' == '0'); // ... false!
console.log(false == undefined); // false
console.log(false == null); // false
console.log(null == undefined); // ... true!
13
Zmienne i stałe
• Zmienne nie posiadają typu (typ mają przypisane wartości)
• Nazwa zmiennej musi być poprawnym identyfikatorem
• Deklaracja zmiennej:
– var – zasięg funkcji
– let – zasięg bloku kodu (ES6)
• Odwołanie do niezadeklarowanej zmiennej powoduje jej poszukiwanie „w górę zasięgów” aż do globalnego obiektu (w przeglądarkach: window)
– Zabronione w trybie strict ("use strict";)
– Odczyt niezainicjalizowanej niezadeklarowanej zmiennej kończy się błędem (dla zadeklarowanej zwraca undefined)
• Deklaracja stałej:
– const (ES6)
14
Zmienne i stałe - Przykłady
var nazwisko = "Kowalski";
var imie = 'Jan';
var _$_dziwna__zmienna_99 = 0xabcd;
var flaga = true;
var innaFlaga = (5 > 7);
var mojeAuto = { marka: 'Fiat', nrRej: 'PNC2781' };
var tablica = [4, 7, 9];
const pi = 3.14;
15
Tablice
• Obsługa tablic za pomocą obiektu Array
– Nie jest konieczne jawne korzystanie z niego
– Możliwość utworzenia i inicjalizacji tablicy konstrukcją []
• Indeksowane od 0 (tylko indeksy liczbowe!)
• Składowa length
• Użyteczne metody (zdefiniowane w prototypie), np. push, pop, sort, forEach, …
• Rzadka indeksacja tablic („dziury w numeracji komórek”)
• Dostęp do elementów tablicy operatorem []
16
Tablice - Przykłady
var miasta = new Array('Berlin','Londyn','Nowy Jork');
var kraje = ['Niemcy','Wielka Brytania','USA'];
var kontynenty = new Array(2);
kontynenty[0] = 'Europa';
kontynenty[1] = 'Ameryka Północna';
console.log(kontynenty.length); // 2
kontynenty[3] = "Afryka"; // 4
console.log(kontynenty.length);
kontynenty.push("Azja");
console.log(kontynenty.length); // 5
console.log(kontynenty);
// ["Europa", "Ameryka Północna", 3: "Afryka", 4: "Azja"]
17
Instrukcje sterujące
• Warunkowe: if, if/else, switch
• Pętle: while, do/while, for, for … in, for … of (ES6)
• Instrukcje używane w pętlach: break, continue
• Obsługa wyjątków: try/catch/finally
18
Pętle for … in i for … of
let car = { marka: "Fiat", cena: 27000};
for (let i in car) {
console.log(i); // "marka", "cena"
}
// for (let i of car) {} // error!
for (let i in car) {
console.log(car[i]); // "Fiat", 27000
}
• for … in iteruje po właściwościach obiektu
– Uwaga: tablice też są obiektami
• for … of iteruje po wartościach właściwości obiektów iterowalnych (tablice, mapy, …)
19
Pętle for … in i for … of (c.d.)
let arr = [3, 5, 7];
for (let i in arr) {
console.log(i); // 0, 1, 2
}
for (let i of arr) {
console.log(i); // 3, 5, 7
}
for (let i in arr) {
console.log(arr[i]); // 3, 5, 7
}
20
Funkcje
• Blok kodu, który wykonuje się gdy zostanie wywołany
– Typowo nazwany blok programu
– Funkcja może być utworzona jako anonimowa
• Funkcja może przyjmować parametry
– Przy wywołaniu można przekazać mniejszą lub większą liczbę parametrów niż zadeklarowano
• Nieustawione zadeklarowane parametry są undefined
• Dostęp do wszystkich parametrów poprzez obiekt arguments
• Funkcja może zwracać wartość (instrukcja return opcjonalna)
• Funkcje same są obiektami (!) – mają pola i metody
• Funkcję można przypisać do zmiennej lub składowej obiektu
• Funkcję można przekazać jako argument funkcji
• Funkcja może zwracać funkcję jako wynik
• Konstruktor - funkcja o specjalnym zastosowaniu (new)
21
Zasięg zmiennych: funkcji i bloku
function testVar() {
var x = 5;
if (x == 5) {
var x = 8; // ta sama zmienna o zasięgu funkcji
console.log(x); // 8
}
console.log(x); // 8
}
function testLet() {
let x = 5;
if (x == 5) {
let x = 8; // nowa zmienna, lokalna dla bloku kodu
console.log(x); // 8
}
console.log(x); // 5
}
22
Programowanie funkcyjne
var t = [3, 8, 9];
t.forEach(function(element) {
console.log(element);
})
var showElements = function(element) {
console.log(element);
};
t.forEach(showElements);
function showElementDetails(element, index, array) {
console.log('a[' + index + '] = ' + element);
}
[2, 25, , 19].forEach(showElementDetails);
23
Programowanie funkcyjne (c.d.)
var numbers = [3, 8, 9];
var squares = numbers.map(function(x) {
return(x*x) ;
})
console.log(squares); // [9, 64, 81]
var numbers = [3, 8, 9];
var squares = numbers.map((x) => {return x*x; } )
console.log(squares); // [9, 64, 81]
var numbers = [3, 8, 9];
var squares = numbers.map( x => x*x )
console.log(squares); // [9, 64, 81]
Funkcje strzałkowe (ES6)
24
Problem: Przechowanie stanu między wywołaniami funkcji z jednoczesnym ograniczeniem dostępu do tego stanu
var counter = 0;
function count() {
counter += 1;
}
count();
count();
// 2
• Zmienna globalna?
– Możliwy dostęp z zewnątrz
• Zmienna lokalna w funkcji?
– Odrębne instancje w kolejnych wywołaniach funkcji
function count() {
var counter = 0;
counter += 1;
}
count();
count();
// ???
* Przykład z w3schools.com
25
Domknięcie (ang. closure)
var count = (function () {
var counter = 0;
return function () {return counter += 1;}
})();
count();
count();
// 2
• Zagnieżdżona funkcja, która ma dostęp do lokalnych zmiennych funkcji nadrzędnej
– Również po zakończeniu działania funkcji nadrzędnej!
– Środowisko przechowuje nielokalne obiekty używane przez funkcję
* Przykład z w3schools.com
26
Operatory rest i spread
• Ten sam zapis (…), ale „odwrotne” działanie
• Rest – łączy parametry funkcji w tablicę
• Spread – rozbija tablicę na listę argumentów funkcji
function add(n1, n2) {
return n1 + n2;
}
var tab = [3, 7];
var result = add(...tab);
console.log(result); // 10
function add(...numbers) {
return numbers.reduce((sum, elem) => sum + elem);
}
var result = add(3, 5, 8);
console.log(result); // 16
27
Obiekty w JavaScript
var person = {firstName: "Marek", lastName: "Wojciechowski",
hello: function() {console.log('Hi!');}};
console.log(typeof(person)); // object
person.hello();
console.log(person.firstName);
console.log(person['firstName']);
person.age = 43;
person.bye = function() {console.log('Bye!');};
person.bye();
console.log(person.age);
delete person.firstName;
console.log(person.firstName); // undefined
• Obiekt = kolekcja dowolnych właściwości
– Właściwości mogą być dodawane i usuwane w czasie życia obiektu
– Właściwości mogą być funkcjami = metody obiektu
28
Operatory destrukturyzacji
• Dla tablic:
[v1, v2, …] = tab
• Dla obiektów:
{p1: v1, p2: v2, …} = obj
• Zamiana wartości zmiennych z użyciem destrukturyzacji
let x = 5;
let y = 7;
[x, y] = [y, x];
console.log(x); // 7
console.log(y); // 5
29
Obiekty jako mapy
var person = {firstName: "Marek", lastName: "Wojciechowski"};
for (var p in person) // firstName, lastName
console.log(p);
person["age"] = 43;
delete person.firstName;
for (var q in person) // lastName, age
console.log(q);
console.log("firstName" in person); // false
console.log("lastName" in person); // true
console.log(Object.keys(person)); // ["lastName", "age"]
• Obiekty mogą być używane jako mapy (tablice asocjacyjne)
– Operator in umożliwia sprawdzenie czy właściwość jest zdefiniowana w obiekcie
– Pętla for … in iteruje po właściwościach
– Object.keys zwraca tablicę kluczy
30
Gettery i settery w JavaScript
var person = {
firstName: 'Marek',
lastName: 'Wojciechowski',
get fullName() {
return this.firstName + ' ' + this.lastName;
},
set fullName (fName) {
var names = fName.toString().split(' ');
this.firstName = names[0] || '';
this.lastName = names[1] || '';
}
}
console.log(person.fullName) // Marek Wojciechowski
person.fullName = 'Jan Kowalski';
console.log(person.firstName); // Jan
console.log(person.lastName) // Kowalski
31
Enumerowalne i nie-enumerowalne właściwości
var person = {firstName: "Marek"};
person.lastName = "Wojciechowski";
person["age"] = 43;
person.sayHi = function () { console.log("Hi!"); };
for (var q in person) // firstName, lastName, age, sayHi
console.log(q);
• Właściwości definiowane w obiektach poprzez przypisanie są enumerowalne
– Widoczne w pętli for … in
– Zwracane przez Object.keys()
• Możliwe jest definiowanie właściwości nie-enumerowalnych za pomocą Object.defineProperty()
32
Object.defineProperty()
var person = {firstName: "Marek", lastName: "Wojciechowski"};
Object.defineProperty(person, "age",
{enumerable: false, writable: true, value: 43});
for (var q in person) // firstName, lastName
console.log(q);
console.log(Object.keys(person)); // ["firstName", "lastName"]
console.log(person.age); // 43
person.age=44;
console.log(person.age); // 44
• Definiuje lub modyfikuje właściwość obiektu
• Składnia: Object.defineProperty(obj, property, descriptor)
• Atrybuty deskryptora:
– Dla danych (w tym funkcji) i akcesorów: configurable, enumerable
– Dla danych (w tym funkcji): value, writable
– Dla akcesorów: get, set
33
Prywatny stan obiektu
var counterObj = (function () {
var counter = 0;
return {
inc: function () {counter += 1;},
value: function () { return counter; }
}
})();
console.log(counterObj.value()); // 0
counterObj.inc();
counterObj.inc();
console.log(counterObj.value()); // 2
• JavaScript nie oferuje kwalifikatorów dostępu do składowych obiektu
• Składowe obiektu są dostępne z zewnątrz
• Efekt „prywatnych pól” można uzyskać poprzez domknięcie
34
Konstruktory
function Rabbit(type) {
this.type = type;
}
var killerRabbit = new Rabbit("killer");
var blackRabbit = new Rabbit("black");
console.log(blackRabbit.type);
// black
• Funkcje wykorzystywane do tworzenia obiektów
– Wywoływane poprzez operator new
– Umożliwiają programową inicjalizację obiektu
– Niejawnie zwracają nowo tworzony obiekt
– Mają dostęp do zmiennej this wskazującej na bieżący obiekt
– Zapewniają jednolitą strukturę i funkcjonalność obiektów danego rodzaju (w momencie ich utworzenia)
35
Metody obiektów
function Rabbit(type) {
this.type = type;
this.speak = function(line) {
console.log("The " + this.type + " rabbit says '" +
line + "'");
};
}
• Metody można dodawać dynamicznie do obiektów
– Obiekty utworzone tym samym konstruktorem mogą później różnić się funkcjonalnością
• Jak zapewnić na starcie ten sam zestaw metod obiektom danego rodzaju?
– Można dodać metody w ciele konstruktora
• Obiekty będą miały własny zestaw tych samych metod
– Lepszym rozwiązaniem jest wykorzystanie prototypu
• Zestaw metod w prototypie współdzielony przez obiekty na nim oparte
36
Prototypy
• Prototyp (ang. prototype) to w JavaScript to obiekt który dla danego obiektu stanowi „zapasowe” źródło właściwości
• Prototypem większości obiektów w JS jest Object.prototype
– Dostarcza kilka metod np. toString(), valueOf()
– Jego właściwości są nie-enumerowalne
• Prototypy tworzą drzewiastą hierarchię
– Odpowiednik hierarchii dziedziczenia klas
• Odczyt prototypu: Object.getPrototypeOf()
• Tworzenie obiektu z prototypu: Object.create(prototyp);
• Zmiana prototypu (ES6): Object.setPrototypeOf()
– Operacja czasochłonna, niezalecana
• Rozróżnienie własnych i odziedziczonych właściwości:
– hasOwnProperty(), Object.getOwnPropertyNames(), Object.getOwnPropertyDescriptor()
37
Konstruktory i prototypy
function Rabbit(type) {
this.type = type;
}
Rabbit.prototype.speak = function(line) {
console.log("The " + this.type + " rabbit says '" +
line + "'");
};
var blackRabbit = new Rabbit("black");
blackRabbit.speak("Hi!!!");
• Konstruktory (jak inne funkcje) posiadają właściwość prototype
– Umożliwia ona przypisanie zestawu metod, które mają być dostępne dla obiektów tworzonych danym konstruktorem
– Uwaga: NIE jest to prototyp funkcji jako obiektu (prototypem funkcji jest domyślnie Function.prototype)
38
this
function testThis() {
return this;
}
console.log(testThis());
// window
• W kontekście globalnym (poza funkcjami) this wskazuje globalny obiekt (w przeglądarce: window)
• W kontekście funkcji, jeśli funkcja nie została wywołana na rzecz konkretnego obiektu, to wartość this zależy od włączenia trybu strict (obiekt globalny lub undefined)
• W kontekście funkcji, jeśli funkcja została wywołana na rzecz konkretnego obiektu, to this wskazuje na ten obiekt
function testThis() {
"use strict";
return this;
}
console.log(testThis());
// undefined
39
this – sposoby wywołania funkcji na rzecz obiektu
• Wywołanie funkcji jako metody obiektu (notacja .)
• Wywołanie przez Function.prototype.call
• Wywołanie przez Function.prototype.apply
• Wywołanie funkcji dowiązanej (Bound Function), utworzonej przez Function.prototype.bind
40
this – Przykłady (pożyczanie metod)
var john = { fname: "John",
greet: function () {
console.log("Hi! I'm " + this.fname);
}
};
john.greet(); // Hi! I'm John
var jane = { fname: "Jane" };
jane.greet = john.greet;
jane.greet(); // Hi! I'm Jane
var greet = john.greet;
greet(); // Hi! I'm undefined
• Metodę obiektu można wywołać na rzecz innego obiektu
– this jest ustawiane w momencie wywołania
41
Jawne ustawienie this przy wywołaniu funkcji
...
var greet = john.greet;
greet(); // Hi! I'm undefined
greet.call(john); // Hi! I'm John
greet.apply(jane, []); // Hi! I'm Jane
• Function.prototype.call
– Pierwszy argument ustawia this
– Argumenty dla funkcji przekazane jako kolejne argumenty call(), oddzielane przecinkami
• Function.prototype.apply
– Pierwszy argument ustawia this
– Argumenty dla funkcji przekazane jako drugi argument apply(), w formie tablicy
42
Funkcje dowiązane
var jane = { fname: "Jane" };
var greet = function () {
console.log("Hi! I'm " + this.fname);
}
greet(); // Hi! I'm undefined
var boundGreet = greet.bind(jane);
boundGreet(); // Hi! I'm Jane
• Tworzone za pomocą Function.prototype.bind
• Funkcja dowiązana pośredniczy w wywołaniu funkcji docelowej
– Ustawia jej this na obiekt wskazany przy tworzeniu funkcji dowiązanej (pierwszym argumentem bind())
– Opcjonalnie dodaje na początku listy argumentów funkcji docelowej argumenty podane przy tworzeniu funkcji dowiązanej (2-gi i kolejne)
43
this w funkcjach zagnieżdżonych (1/5)
var person =
{ fname: "John",
greet: function () {
var innerGreet = function() {
console.log("Hi! I'm " + this.fname);
};
innerGreet();
}
};
person.greet(); // undefined
• Problem: Funkcja zagnieżdżona w metodzie nie przejmuje od niej ustawienia this (this nie należy do domknięcia)
44
this w funkcjach zagnieżdżonych (2/5)
var person =
{ fname: "John",
greet: function () {
var self = this;
var innerGreet = function() {
console.log("Hi! I'm " + self.fname)
};
innerGreet();
}
};
person.greet(); // John
• Rozwiązanie 1: Jawne przekazanie this do funkcji zagnieżdżonej przez pomocniczą zmienną w ramach domknięcia
– Idiom „self = this” (that, me, …)
45
this w funkcjach zagnieżdżonych (3/5)
var person =
{ fname: "John",
greet: function () {
var innerGreet = function() {
console.log("Hi! I'm " + this.fname);
};
innerGreet.call(this);
}
};
person.greet(); // John
• Rozwiązanie 2: Jawne przekazanie this poprzez wywołanie funkcji zagnieżdżonej funkcją call lub apply
46
this w funkcjach zagnieżdżonych (4/5)
var person =
{ fname: "John",
greet: function () {
var innerGreet = function() {
console.log("Hi! I'm " + this.fname);
};
var boundInnerGreet = innerGreet.bind(this);
boundInnerGreet(); }
};
person.greet(); // John
• Rozwiązanie 3: Wykorzystanie funkcji dowiązanej
47
this w funkcjach zagnieżdżonych (5/5)
var person =
{ fname: "John",
greet: function () {
var innerGreet =
() => { console.log("Hi! I'm " + this.fname) };
innerGreet();
}
};
person.greet(); // John
• Rozwiązanie 4: Wykorzystanie funkcji strzałkowej (ES6)
– Funkcje strzałkowe nie posiadają własnego ustawienia this (używana jest wartość this z otaczającego kontekstu)
48
Klasy (ES6)
class Shape {
constructor (id, x, y) {
this.id = id
this.move(x, y)
}
move (x, y) {
this.x = x
this.y = y
}
}
var shape = new Shape(101, 10, 20);
• Inny sposób definiowania funkcji konstruktorowych z prototypami („mostly syntactic sugar”)
– Składnia bardziej naturalna dla programistów migrujących z języków C++, C#, Java
– Klasy w JS nie są obiektami, tylko funkcjami
• Nie ma w JS:
– przeciążania metod i konstruktorów,
– deklaracji pól
– widzialności składowych
– specyfikacji typów
49
Dziedziczenie (ES6)
class Rectangle extends Shape {
constructor (id, x, y, width, height) {
super(id, x, y)
this.width = width
this.height = height
}
}
class Circle extends Shape {
constructor (id, x, y, radius) {
super(id, x, y)
this.radius = radius
}
}
• Bardziej intuicyjna składnia niż przy dziedziczeniu funkcjonalności obiektów z prototypów
50
Metody statyczne (ES6)
class First {
static show () {
console.log("First")
}
}
First.show()
class Second extends First {
static show() {
console.log("Second extends")
super.show()
}
}
Second.show()
• Wołane ZAWSZE na rzecz klasy, dziedziczone
51
Gettery i settery w klasach (ES6)
class Rectangle {
constructor (width, height) {
this._width = width
this._height = height
}
set width(width) { this._width = width}
get width() { return this._width}
set height(height) { this._height = height}
get height() { return this._height}
get area() { return this._width * this._height }
}
var r = new Rectangle(5, 3)
r.width = 6
console.log(r.area)
• Problem prywatności „wewnętrznych” składowych
52
Obsługa wyjątków
• Instrukcja throw
– Rzucić można wszystko (np. number, string), ale zalecane rzucanie obiektów Error lub podtypów
• Natywne typy wyjątków
– EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError
• Instrukcja try/catch/finally
– Po try musi być blok catch lub finally (mogą być oba)
– finally wykonuje się niezależnie od tego czy w bloku try wystąpił wyjątek czy nie
– W bloku catch można sprawdzić rodzaj wyjątku operatorem instanceof
53
Wyjątki – Przykład (ES6)
class MyError extends Error {
constructor(m) {
super(m);
this.name = this.constructor.name;
}
}
try {
throw new MyError("Fatal error");
}
catch (e) {
if (e instanceof MyError) {
console.log(e.message); // Fatal error
console.log(e.name); // MyError
}
else
console.log('Unknown Error');
}
54
Terminologia dotycząca JS
• Minification
• Obfuscation
• Vanilla JS
• Unobtrusive JS
• Polyfill
55
JSON (JavaScript Object Notation)
• Tekstowy format danych do reprezentacji i transmisji obiektów zawierających pary atrybut:wartość
• Wywodzi się z języka JavaScript (serializacja obiektów)
• Obecnie parsowanie i generacja JSON wspierane przez różne języki programowania
• Wiele różnych zastosowań
– RESTful Web Services
– AJAX (AJAJ)
– Bazy danych NoSQL
• Typ MIME: application/json
• Rozszerzenie pliku: .json
56
Składnia i typy danych JSON
• Podstawowe typy danych
– Number (NaN niedozwolony, nierozróżnialne całk. i zmiennoprzec.))
– String (Unicode, cudzysłowy jako ograniczniki, \ jako escape)
– Boolean (true/false)
– Obiekty: { atrybut1 :wartość1, atrybut2 :wartość2, … }
– Tablice: [element1, element2, … ]
– null
• Białe spacje nie mają znaczenia
• Brak możliwości komentarzy
57
JSON - Przykład
{
"firstName": "Marek",
"lastName": "Wojciechowski",
"age": 43,
"married": true,
"languages": [
"Polish",
"English",
"German",
"Russian",
"Spanish"
],
"phoneNumber": {
"type": "mobile",
"number": "666 444 333"
}
}
58
Generacja JSON w JavaScript
• JSON.stringify(value[, replacer[, space]])
– Zwraca String reprezentujący przekazaną wartość
– Opcjonalne parametry umożliwiają:
• Zmianę zachowania procesu serializacji
• Dodanie białych spacji dla czytelności
– Uwagi o sposobie działania:
• Nie gwarantuje zachowania kolejności właściwości obiektów niebędących tablicami
• Obiekty typów String, Boolean i Number są zamieniane na odpowiadające wartości proste
• Wartości undefined i funkcje są:
– W obiektach: pomijane
– W tablicach: zamieniane na null
• Nie-enumerowalne właściwości (niewidoczne dla pętli for … in) są ignorowane
59
Parsowanie JSON w JavaScript
• JSON.parse(text[, reviver])
– Wynikiem jest Object
– Gdy parsowany tekst nie jest poprawnym zapisem JSON, rzucany jest wyjątek SyntaxError
– Opcjonalna funkcja reviver umożliwia transformację parsowanych wartości
• eval()
– Funkcja do wartościowania dynamicznych wyrażeń
– Wykorzystuje fakt, że składnia JSON jest (prawie) podzbiorem składni JavaScript
– Potencjalnie niebezpieczna jeśli tekst do parsowania pochodzi z niepewnego źródła (np. zagnieżdżone funkcje)
60
Parsowanie i generacja JSON w JavaScript - Przykład
var person = {firstName: "Marek", lastName: "Wojciechowski",
age: 43, salary: undefined, married: true,
languages: ['Polish','English','German']};
console.log(JSON.stringify(person));
var json = '{"firstName":"Marek","lastName":"Wojciechowski",' +
'"age":43,"married":true,' +
'"languages":["Polish","English","German"]}';
person1 = JSON.parse(json);
console.log(person1.firstName);
person2 = eval('(' + json + ')'); // w ogólności niebezpieczne
console.log(person2.firstName);
61
Modularność kodu w JavaScript
• Przed ES2015
– Wzorzec programowy modułu
– Niekompatybilne ze sobą standardy opracowane przez społeczność:
• CommonJS
– Implementacja w node.js
– Zaprojektowany dla synchronicznego ładowania modułów
– Dla zastosowań serwerowych
• Asynchronous Module Definition (AMD)
– Popularna implementacja: RequireJS
– Zaprojektowany dla asynchronicznego ładowania modułów
– Dla zastosowań w przeglądarce
• Od ES2015
– Modularność wbudowana w język (składnia export / import)
62
Cele stosowania modułów
• Podział kodu na małe pliki, z których każdy zawiera kod odpowiedzialny za pewną funkcjonalność
• Współdzielenie kodu między aplikacjami
• Używanie gotowych, sprawdzonych rozwiązań
• Ukrywanie szczegółów implementacyjnych
• Separacja „przestrzeni nazw” (nazwy zmiennych, funkcji, itd.) dla kodu z różnych źródeł
– Uniknięcie „wyciekania” nazw do globalnej przestrzeni nazw, a przez to możliwych kolizji nazw
• Zarządzanie zależnościami
63
Wzorzec modułu
• Cel stosowania: lokalność nazw importowanego kodu
• Wykorzystane mechanizmy:
– immediately-invoked function expression (IIFE, „iffy”)
– domknięcie (ang. closure)
var module = (function () {
// private
var multiply = function (x,y) { return x * y; };
var value = 5;
...
// public
return {
multiplyByValue: function (num) {
return multiply(num, value);
},
...
}
};
})();
console.log(module.multiplyByValue(10));
64
Moduły ES2015
• Standard natywnych modułów w ramach języka JavaScript
• Cechy:
– Dla zastosowań serwerowych i w przeglądarce
– Przewiduje synchroniczne i asynchroniczne ładowanie modułów
– Zawartość modułu domyślnie w trybie strict
– Zawartość domyślnie prywatna; to co ma być udostępnione na zewnątrz należy jawnie wyeksportować (export)
– Statyczny charakter modułów (tj. nie trzeba uruchomić kodu modułu by określić co eksportuje)
– Deklaratywne (import) lub programowe (System.import()) ładowanie modułów
65
Moduły ES2015 – export / import
import { square, PI } from './math';
console.log(PI*square(5));
export const PI = 3.14;
export function square(x) {
return x * x;
}
main.js
math.js
• Moduł może wskazać swój jeden, najważniejszy składnik jako domyślny eksport
– Składnia głównie z myślą o modułach eksportujących jeden składnik, ale oprócz domyślnego eksportu moduł może eksportować również inne składowe jako eksporty nazwane
66
Moduły ES2015 – domyślne eksporty
import myFunction from 'myFunction';
import MyClass from 'MyClass';
console.log(myFunction(5));
var obj = new MyClass();
export default class {
}
main.js
MyClass.js
export default function (x) {
return x * x;
}
myFunction.js
67
Przetwarzanie asynchroniczne w JavaScript
• Przetwarzanie asynchroniczne jest zdecydowanie preferowane przy odwołaniu do zewnętrznych zasobów (np. Ajax)
– Silniki JavaScript są jednowątkowe („by design”)
• Wyjątek: worker threads
– Wywołania synchroniczne blokowałyby działanie programu/strony
• Sposoby implementacji asynchronicznego kodu w JS:
– Funkcje wywołań zwrotnych (ang. callback)
– Obietnice (ang. promise) – od ES2015
– Składnia async / await – od ES2017
• Bazuje na obietnicach i generatorach
– Programowanie reaktywne
• Reactive Extensions, RxJS
– Generatory – od ES2015
68
Wywołania zwrotne
• Funkcja wołana asynchronicznie otrzymuje jako argumenty funkcję, która będzie wywołana po zakończeniu przetwarzania
– Callback otrzymuje wynik przetwarzania jako swój argument
– Może być przekazana więcej niż jedna funkcja callback, np. jedna do obsługi zakończenia przetwarzania sukcesem, druga do obsługi błędu przetwarzania
– Zagnieżdżanie wywołań zwrotnych prowadzi do nieczytelnych programów (tzw. callback hell)
setTimeout(function() { console.log("callback!");}, 1000);
console.log("normal flow!");
setTimeout(() => { console.log("callback!");}, 1000);
console.log("normal flow!");
69
Obietnice (ang. Promise)
• Obietnica = obiekt reprezentujący zakończenie w przyszłości asynchronicznego przetwarzania (pozytywne lub z błędem)
function doSomething() {
return new Promise((resolve, reject) => {
...
if (success) {
resolve(result)
} else {
reject(error_info)
}
})
}
const promise = doSomething();
promise.then(successCallback, failureCallback);
// lub
// doSomething().then(successCallback, failureCallback);
70
Łańcuchowanie obietnic
• Rozwiązanie typowego problemu sekwencji operacji asynchronicznych, gdzie każda kolejna operacja ma się rozpocząć dopiero po zakończeniu poprzedniej
• Metoda then() zwraca nową obietnicę, która staje się zrealizowana nie tylko po zakończeniu oryginalnej obietnicy, ale dodatkowo po zakończeniu jednego z callbacków przekazanych do then()
• catch(errorCallback) = then(null, errorCallback)
doFirstThing().then(function(firstResult) {
return doSecondThing(firstResult);
})
.then(function(secondResult) {
...
})
.catch(handleFailureCallback);
71
async / await
• Lukier syntaktyczny nałożony na obietnice
• Kolejny krok w stronę większej czytelności kodu
– Sekwencja asynchronicznych operacji na poziomie kodu źródłowego wygląda jak program sekwencyjny
async function complexAsyncOperation() {
try {
const firstResult = await doFirstThing();
const secondResult = await doSecondThing(firstResult);
...
} catch(error) {
handleFailureCallback(error);
}
}
72
Programowanie reaktywne
• ang. Reactive Programming
• Deklaratywny paradygmat programowania dotyczący:
– Przetwarzania strumieni danych
– Propagacji zmian
• „książkowy” przykład: a := b + c w podejściu imperatywnym i reaktywnym
• Reactive Extensions (ReactiveX)
– Zestaw narzędzi pozwalający imperatywnym językom na obsługę sekwencji (strumieni) danych
– API dla asynchronicznego programowania w kontekście obserwowalnych strumieni danych
– RxJS = Reactive Extensions dla języka JavaScript
73
ReactiveX: strumienie i obserwatorzy
• Obserwowalny (ang. observable) strumień
– Emituje 3 rodzaje zdarzeń:
• next
• error
• complete
• Obserwator (ang. observer)
– Subskrybuje obserwowalny strumień
– Otrzymuje kolejne elementy ze strumienia pojedynczo (zazwyczaj poprzez funkcję callback)
– Przetwarza dane ze strumienia sekwencyjnie:
• Kolejny element nie zostanie mu dostarczony do powrotu z callbacka przetwarzającego poprzedni element
• Dane przetwarzane z zachowaniem porządku ze strumienia
74
RxJS - Przykład
• Przykład z https://github.com/Reactive-Extensions/RxJS
/* Get stock data somehow */
const source = getAsyncStockData();
const subscription = source
.filter(quote => quote.price > 30)
.map(quote => quote.price)
.subscribe(
price => console.log(`Prices higher than $30: ${price}`),
err => console.log(`Something went wrong: ${err.message}`)
);
/* When we're done */
subscription.dispose();
75
RxJS – Tworzenie i konwersja do obserwowalnych strumieni
• Tworzenie (jedna z możliowości)
• Konwersja (przykłady obsługiwanych źródeł)
var myObservable = new Rx.Subject();
myObservable.subscribe(value => console.log(value));
myObservable.next('first');
myObservable.next('second');
// z listy wartości
Rx.Observable.of('first', 'second');
// z tablicy
Rx.Observable.from(['first', 'second']);
// ze zdarzenia
Rx.Observable.fromEvent(document.querySelector('#myButton'),
'click');
// z obietnicy
Rx.Observable.fromPromise(fetch('/stockData'));
76
Generatory
function* testGenCreator() {
var index = 0;
while(index < 3)
yield index++;
}
var gen = testGenCreator();
console.log(gen.next()); // {done: false, value: 0}
console.log(gen.next()); // {done: false, value: 1}
console.log(gen.next()); // {done: false, value: 2}
console.log(gen.next()); // {done: true, value: undefined}
• Generator = funkcja, która: – Może wygenerować wiele wartości
– Pozwala na iterację po zwracanych wartościach
– Wstrzymuje swoje działanie po wygenerowaniu wartości
– Przechowuje swój stan po wstrzymaniu
– Umożliwia przeplot z innym kodem w trakcie swojego działania
– Może być wykorzystana do generowania skończonych lub nieskończonych sekwencji wartości
77
Generatory - Przykład
function* fibonacci() {
var fn1 = 0;
var fn2 = 1;
while (true) {
var current = fn1;
fn1 = fn2;
fn2 = current + fn1;
var reset = yield current;
if (reset) { fn1 = 0; fn2 = 1; }
}
}
var sequence = fibonacci();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
console.log(sequence.next().value); // 3
console.log(sequence.next().value); // 5
console.log(sequence.next().value); // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2 * Przykład z developer.mozilla.org