ES2015
Poniższy opis standardu ES6 (ES2015) jest jedynie krótkim przeglądem (inspirowanym przez https://babeljs.io/learn-es2015/ ). Zainteresowanych bardziej szczegółowym opisem odsyłamy do podręcznika Nicholasa C. Zakasa "Ecmascript 6. Przewodnik po nowym standardzie języka Javascript".
let i const
Jak już wspomniano, zmienne zadeklarowane słowem var - są widoczne w całym bloku funkcji niezależnie od miejsca w którym zadeklarowano (hoisting)!!!
Jako alternatywę wprowadzono dwa słowa kluczowe: let i const.
Zmiennych zadeklarowanych z ich pomocą nie można deklarować ponownie (deklarować zmiennej o tej samej nazwie). Czyli ogranicza to dynamiczne typowania.
Zmienna const musibyć dodatkowo zainicjalizowana - i to tylko raz (nie zmienia wartości).
Najważniejsze jest to, że tak zadeklarowane zmienne są widoczne tylko w bloku w którym je zadeklarowano (nie tylko w bloku funkcji - ale na przykład w pętli). Błąd nie zostanie więc zgłoszony w sytuacji, gdy deklaracja let utworzy nową zmienną o takiej samej w blokach nadrzednym i podrzednym (np. w funkcji i pętli wewnątrz tej funkcji). To będą dwie różne zmienne o tej samej nazwie.
Użycie deklaracji const uniemożliwia modyfikację wiązania (wartości zmiennej), ale nie wartości obiektu na jaki wskazuje.
Dostęp do zmiennej zadeklarowanej za pomocą słów kluczowych let lub const nie jest (w przeciwiństwie do var) możliwy przed pojawieniem się deklaracji:
> if (typeof(v) != 'undefined') {console.log('zmienna widoczna');} var v = "wartosc";
zmienna widoczna
> if (typeof(l) != 'undefined') {console.log('zmienna widoczna');} let l = "wartosc";
ReferenceError: lzmienna is not defined
>
Przykład:
> if (true) {
let zmienna = "wartosc";
const stala={v: 1};
stala.v=2;
console.log(stala.v);
console.log(zmienna);
}
2
wartosc
> console.log(stala.v);
ReferenceError: stala is not defined
> console.log(zmienna);
ReferenceError: zmienna is not defined
>
Zaleca się stosowanie wszędzie, gdzie to tylko możliwe - deklaracji const.
Pętla for/of
W standardzie ES6 wprowadzono pętlę for/of jako alteratywę do for/in:
<button onclick="petla_for_of()">Kliknij</button>
<p id="wynik"></p>
<script>
function petla_for_of() {
var lista = ['a','b','c'];
var text = "";
for (let x of lista) {
text += x + " ";
}
document.getElementById("wynik").innerHTML = text;
}
</script>
Wynik:
a b c
W tej pętli indeks (x) przyjmuje wartości z listy (a nie klucze jak w pętli for/in). Przy okazji pokazano jak w typowy sposób deklarujemy w pętli zmienne przy pomocy "let" .
Funkcje strzałkowe
Zamiast function(parametry){instrukcje} możemy zapisać (parametry)=>{instrukcje}. Przyczym nawiasy są obowiązkowe tyko wtedy, gdy ich brak prowadzi do niejednoznaczności.
Przykład:
let lista=[1,2,3,5];
let suma=0;
for (n of lista.map( (x)=> {return x*x*x;} ) ) suma+=n;
console.log(suma);
// wersja uproszczona:
let lista=[1,2,3,5];
let suma=0;
for (n of lista.map( x => x*x*x ) ) suma+=n;
console.log(suma);
Wynik: 161
Funkcja użyta w jako parametr dla map liczy sześcian liczby. Mamy więc tutaj sumę szcześcianów.
Poza uproszczeniem zapisu, dodatkową (a może główną) zaletą funkcji strzałkowych jest to, że słowo this zachowuje się w nich w sposób bardziej intuicyjny - funkcje strzałkowe nie zmienają jego znaczenia. A więc użyte wewnątrz klasy/obiektu zagnieżdżone funkcje stale odnoszą się do tego smego this.
Przykład:
let lista=[1,2,3,5];
let suma=0;
const licz = () => {
for (n of lista.map( x => {this.suma+=x*x*x} ) );
console.log(suma);
};
licz()
console.log(suma);
Na wynik dostaniem dwukrotnie 161.
Wartość domyślna parametrów
W ES6 wprowadzono wartości domyślne parametrów funkcji. Na przykład:
function kwota(ile, waluta='PLN', przed=false) {
return przed ? waluta+ile.toString() : ile.toString()+waluta;
}
console.log(kwota(12));
console.log(kwota(10,'$',true));
Możemy przy wywołaniu funkcji podać tylko cęść parametrów.
Uwaga: opuszczenie części parametrów, a podanie następnych może prowadzić do błędów:
> console.log(kwota(10,przed=true));
10true
Klasy i obiekty
Przykład:
class Osoba {
constructor(imie,nazwisko){
this.imie=imie;
this.nazwisko=nazwisko;
}
przedstawSie(){
console.log(this.imie+' '+this.nazwisko);
}
}
class Pracownik extends Osoba{
constructor(nr,imie,nazwisko) {
super(imie,nazwisko);
this.nr=nr;
}
}
var dyrektor = new Pracownik(1,"Jan","Kowalski");
dyrektor.przedstawSie();
for (wlasnosc in dyrektor) { console.log(wlasnosc);}
Wyjście:
Jan Kowalski
imie
nazwisko
nr
Na co należy zwrócić uwagę:
- Klasa jest widoczna dopiero od chwili deklaracji (miejsce w programie, gdzie jej nie widać nazywa się czasem "tymczasową strefą martwą", a widoczność przed deklaracją - jak w przypadku funkcji - hoistingiem);
- W kodzie klasy obowiązuje "tryb ścisły" ("strict"). Czyli sprawdzane jest restrycyjnie, czy użyte zmienne są zadeklarowane, czy nie próbujesz użyć dwókrotnie takiej samej nazwy parametru funkcji, lub nazwy klasy do nazwania funkcji / metody etc...
- Wywołanie konstruktora klasy inaczej niż w instrukcji tworzenia obiektu (new Konstruktor()) powoduje zgłoszenie błędu;
- Można wyliczać nazwy własności instrukcji for/in, ale nie dotyczy to metod.
Symbole
W ES6 wprowadzono typ który działa podobnie jak symbole true i false. One nie są używane w obliczeniach, ale wyłącznie do sprawdzenia, czy jakaś zmienna ma taką wartość (w TypeScript wprowadzono dodatkowo typ wyliczeniowy, który ma taką własność). Przykład:
> let symbol=Symbol();
undefined
> let symbol2=Symbol('s2');
undefined
> String(symbol2);
'Symbol(s2)'
> String(symbol);
'Symbol()'
>
Rozpakowanie struktury
W niektórych językach programowania (np. Python) istnieje możliwość potraktowania kolekcji (struktury) danych jako zbioru odrębnych zmiennych. ES6 także wprowadza taką funkcjonalność:
let osoby = [
{ imie : "Jan", nazwisko : "Kowalski"},
{ imie : "Józef", nazwisko : "Nowak"}
];
for ({ imie, nazwisko } of osoby) {
console.log(imie+" "+nazwisko);
}
Możliwości rozpakowania w Javascript mogą być wykorzystane także do struktur zagnieżdżonych.
Operator rozpakowania i parametr reszty
Dodatkową możliwością wprowadzoną w ES6 są parametry reszty (rest parameter). Jest to parametr krujący listę dowolnej ilości parametrów.
Na przykład:
function sumuj(czynnik, ...reszta) {
for (liczba of reszta) czynnik+=liczba;
return czynnik;
}
console.log(sumuj(2,4,6,9));
Po trzech kropkach podajemy identyfikator pod którym kryje się lista pozostałych parametrów.
Podobna składnia jest używana dla operatora rozpakowania (spread). Stosuje się go tam gdzie mamy wyliczyć wszystkie elementy listy lub struktury.
Przykład:
> let lista = ['b', 'c'];
> console.log(['a',...lista,'d','e'].concat());
[ 'a', 'b', 'c', 'd', 'e' ]
Podobnie możemy zrobić ze strukturami:
> let struktura = {2:'b', 3:'c'};
> let struktura2={1:'a', ...struktura,4:'d'};
> for (i in struktura2) {
console.log(i.toString()+' => '+struktura2[i]);
}
1 => a
2 => b
3 => c
4 => d
Iteratory i generatory
Pętla for / of przebiega przez wszystkie elementy listy. Zamiast listy może być dowolny iterator - czyli specjalny obiekt z wbudowaną funkcją next. Generator to funkcja tworząca iteratory. Oznacza się go gwiazdką po słowie function. Kolejne elementy iteracji zwracane są instrukcją yield https://developer.mozilla.org/pl/docs/Web/JavaScript/Referencje/Operatory/yield:
Przykład:
> function* generator() {
for (c=0; c<=9; c++) yield c;
}
> var iterator_cyfr=generator();
> var suma=0;
> for (c of iterator_cyfr) suma+=c;
45
Najważniejsze jest to, że ciało funkcji generatora (tu:generator()
) nie jest wykonywane w chwili utworzenia iteratora (tu:var iterator_cyfr=generator();
), ale dopiero gdy wywołamy metodę next()
iteratora. W powyższym przykładzie dzieje się to (niejawnie) w kolejnych iteracjach pętli for/of. Słowo kluczowe yield oznacza, że w dane po nim następujące są zwracane do iteratora, a dalsza część ciała funkcji jest wykonywana dopiero przy następnym wywołaniu next()
.
Więcej informacji na temat generatorów: http://2ality.com/2015/03/es6-generators.html
Moduły
Mpduły to wydzielone w odrębnych plikach fragmenty kodu Javascript. Ich zawartość może być dołączana poleceniem import. Nie są jednak dołączane wszystkie zaimplementowane w module zmienne i funkcje, ale jedynie te, które zostaną jawnie wyeksportowane i zaimportowane.
Przykład:
Moduł m1.js
const klucz=9;
export function szyfr(zmienna) { return zmienna + klucz; }
Moduł m2.js
import { szyfr } from "./m1.js";
console.log(szyfr(8));
Łańcuchy znaków i wyrażenia regularne
ES6 wprowadza dodatkowy typ łańcuchów znaków - w apostrofach odwróconych (` - ang. backtick). Mogą one zawierać oznaczenie miejsc wstawienia danych. Mogą też (w przeciwieństwie do tradycyjnych łańcuchów znaków) obejmować więcej niż jeden wiersz.
Przykład:
> const witaj = `Witaj ${name}
na naszej stronie`;
> let name="Jan Kowalski";
> console.log(witaj);
Witaj Jan Kowalski
na naszej stronie
Zamiast prostych zmiennych mogą być wstawiane wyniki złożonych wyrażeń, obiekty, a nawet wzorce podrzędne.
Przykład (ciąg dalszy poprzedniego przykładu):
> let page="Nasza strona";
> const h = `Witaj ${name}
na stronie ${page}
[${ new Date().toISOString().slice(0, 10) }]`;
> console.log(h);
Witaj Jan Kowalski
na stronie Nasza strona
[2018-05-20]
Wprowadzono także możliwość definiowania własnych funkcji przetwarzających takie szablony. Przykład:
> function tagH1(literals, ...substitutions) {
let result="<h1>";
substitutions.forEach( (s,i) => { result+=literals[i]+s; } );
return result+"</h1>";
}
> console.log(tagH1`Witaj ${name} na ${page}`);
<h1>Witaj Jan Kowalski na Nasza strona</h1>
Objaśnienie:
- funkcja forEach symuluje pętlę - wywołuje ona dla każdego elementu listy funckję (tu: strzałkową) - podaną w parametrze; przekazuje element i jego indeks;
- funkcja tagH1 ma zwrócić przetworzony łańcuch znaków
- zapis j
`nazwaFunkcji
literał```oznaca wywołanie funkcji (nazwaFunkcji) z podzielonym na elementy literałem (parametr literals) i elementami do wstawienia (substitutions).
Obietnice i przetwarzanie asynchroniczne
W przeglądarkach wiele czynności odbywa się asynchronicznie. Na przykład program "ściąga" dane w tle - w czasie, gdy użytkownik przegląda stronę. W Javascript tego typu asynchroniczne przetwarzanie jest obsługiwane przez funkcje powrotu (callback), wywoływane po skończeniu asynchronicznie wykonywanej czynności. ES6 wproadza jednak nowe rozwiązanie o nazwie Promise (obietnica): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Przykład:
console.log('Start...',Date.now());
const delay=5000;
(new Promise(
(resolve, reject) => { setTimeout( resolve, delay ); }
)
).then(
() => { console.log('KONIEC',Date.now()); }
);
Objaśnienie:
- Tworzony jest obiekt obietnicy (new Promise), którego zainicjowanie polega na ustawieniu opóźnienia (setTimeout) o 5000 milisekund (zmienna delay).
- funkcja then wywoływana na rzecz tego obiektu implementuje działania podjęte wówczas, gdy obietnica zostanie spełniona (minie 5000 milisekund)
Obiektnica może znajdować się w jednym z następujących stanów:
- spełniona/rozwiązana [fulfilled/resolved] - zadanie zrealizowane i zwrócona prawidłowa wartość
- odrzucona [rejected] – zadanie nie zrealizowane (wyjątek, nieprawidłowa wartość etc...);
- oczekująca [pending] – utworzono obietnicę, ale jeszcze nie ma wyniku;
W powyższym prostym przykładzie nie uwzględniono sytuacji odrzucenia. Obietnica wywołuje wówczas funkcję reject. Szczegółowe objaśnienie: http://devenv.pl/obietnice-promises-podstawy-jezyka-javascript/
Inne
ES6 wprowada także szereg pomniejszych zmian, jak rozszerzenie funkcjonalności niektórych obiektów (z najważniejszych: klasa Number ma teraz własność isInteger), nowe typy danych (zbiory Set) czy dostęp do funkcji interpretera ("silnika") przez tak zwane proxy.