Mastering Javascript - ECMAScript 6 cz. 2

Pierwsza część przyniosła mi sporo radości jak na teorię, dzisiaj zapraszam na następną partię wiedzy :) Zwrócono mi uwagę, że ES6 ma kilka lat i powstały nowe wersje, spokojnie, dojdziemy do tego w kolejnych częściach serii.

Destructuring  (destruktyzacje)

Najprościej ujmując destrukturyzacja upraszcza przypisywanie danych z nieprymitywnych struktur takich jak obiekt czy tablice. Wcześniej wymagało to kilku linijek kodu, teraz jest absurdalnie proste.

Przypisywanie wartości z tablicy do zmiennych kiedyś wymagało iteracji po tablicy albo odniesienia się do indeksu. Spójrzcie poniżej na wersje z ES6:

[a, b] = [2, 4]
// a = 2, b = 4
[c, ,d] = [1, 2, 3, 4]
//c = 1, d = 4

Możemy się również zagłębić w podobne działania na obiektach. Mamy funkcje, która zwraca obiekt i możemy dynamicznie przypisać jej właściwości do nowych zmiennych.

const fun = () => {
    return {
        kot: 'mruczący',
        pies: 'włochaty'
        kroliczek: 'puchaty'
    }
}
const { kot, pies, kroliczek } = fun()
// kot zwróci 'mruczący' itd.

Dotyczy to przypisania na wszystkich poziomach, nawet gdyby 'mruczący' był kolejnym obiektem. Obiekt do którego przypisujemy może mieć także wartości domyślne:

const fun = () => {
    return {
        kot: 'mruczący',
        kroliczek: 'puchaty'
    }
}
const { kot = 'rudy', pies = 'szczęśliwy', kroliczek = 'śliczny' } = fun()
// kot zwróci 'mruczący', pies 'szczęśliwy', bo nie miał żadnej wartości w obiekcie, a króliczek 'śliczny'

Podobne zmiany nastąpiły w argumentach funkcji. Spójrzcie tylko na to:

function objectFun({ imie, wiek, plecak }) {
    console.log(imie, wiek, plecak)
}

objectFun({ imie = 'Kamila', wiek = 'wystarczajacy', plecak = 'pusty'})

function objectFun([ imie, wiek, plecak ]) {
    console.log(imie, wiek, plecak)
}

Symbol - nowy typ danych

Od kiedy Javascript przejął logikę aplikacji, stanął przed szeregiem nowych wyzwań w wyniku których powstał kolejny typ prymitywny Symbol, który - bardzo upraszczając - jest generatorem unikatowych stałych.

Deklaracja wygląda w tej sposób:

const klucz = new Symbol()

//można też użyć opisu, który przydaje się w debugowaniu, ale nie ma żadnego znaczenia dla działania symbolu
const klucz = new Symbol('id')

Zostały one utworzone w określonym celu, można o tym przeczytać szczegółowo w glosariuszu Mozilli. W skrócie docelowo miały być to generatory kluczy do obiektów.

const key = new Symbol('id')
const object[key] = () => { console.log('Hello') }

Myślę, że działanie symboli najlepiej pokazuje:

Symbol('id') === Symbol('id') // -> zwróci false

Żeby odwołać się do wartości w takim obiekcie, musisz mieć zapisany klucz.

const key = new Symbol('id')
const object[key] = () => { console.log('Hello') }

//...
object[key] // -> nasza funkcja
const innyObiekt = object; // przekazujemt referencję
console.log(innyObiekt[key]) -> nasza funkcja

Kluczową różnicą dla symboli jest to, że w istocie nie są zwykłymi kluczami. Metoda getOwnPropertyNames() nie zwróci Symboli jako właściwości. Istnieje specjalna metoda Object.getOwnPropertySymbols().

Ostatnia pozostaje kwestia symboli globalnych, które można identyfikować po nazwie (nazwa to nie to samo co opis!). Tworzy się je metodą keyFor na obiekcie Symbol.

const key = Symbol.for('kot')
Symbol.keyFor(key) === "kot" // -> true

Zasada działania jest prosta: jeśli pod tą nazwą istnieje symbol, zwróci go. Jeśli nie to utworzy go i zwróci.

Internationalization

Uwielbiam gdy rozwiązania stają się elastyczne. Szczególnie, że my, Polacy, mamy jeden z najbardziej poetyckich języków na świecie. I jeden z najtrudniejszych.

Ale jak to ma się do Javascript? Wszyscy wiemy, że porównując litery pierwszy według kolejności alfabetycznej będzie mniejszy od porównywanego.

'a' < 'b' // -> true

Ale jak to ma się do polskich znaków diakrytycznych? Gdzie jest ą, ę? Sprawdźmy.

console.log('a' < 'b')
console.log('b' < 'ą')
console.log('a' < 'ą')
console.log('z' < 'ą')

Wszystkie powyższe wyrażenia zwrócą true. Dlaczego? Ponieważ wszystkie znaki specjalne (w tym diakrytyczne) mają większą wartość niż standardowy alfabet łaciński.

To jak sortować w języku polskim? Nadpisując funkcję sort? W ES6 już nie, mamy specjalny namespace do internacjonalizacji :) A w nim funkcję rozwiązującą nasz problem:

    let literki = ['a', 'ą', 'b', 'z', 'ź', 'ż'];
    const porownywarka = new Intl.Collator('pl');
    literki.sort()
    console.log(literki); // -> Array(6) [ "a", "b", "z", "ą", "ź", "ż" ]
    literki.sort(porownywarka.compare);
    console.log(literki); // -> Array(6) [ "a", "ą", "b", "z", "ź", "ż" ] 

Najpierw deklarujemy zupełnie randomową tablicę znaków zawierającą popularne polskie znaki diakrytyczne. Potem tworzymy obiekt porównujący w języku polskim. Zawiera on metodę compare, która - jak to w sortowaniach bywa - zwraca wartość dodatnią, zero lub ujemną w zależności od porównywania.

Jeśli nigdy nie nadpisywaliście domyślnej funkcji sortującej polecam analizę tych linków:

Jako argument podaje się funkcje, która przyjmuje i zwraca określone wartości. Zupełnie jak w naszym przykładzie :)

Wracając do tematu: Zauważycie tam dwa sortowania: zwykle, które błędnie sortuje polskie znaki i wynik związany z compare, poprawny.

Internationalization - formatowanie liczb

W tym namespace'ie istnieje również formatowanie liczb, przeprowadza się je za pomocą funkcji format:

let polski = new Intl.NumberFormat("pl-PL");
polski.format(9874563.21)
let angielski = new Intl.NumberFormat("en-US");
angielski.format(9874563.21)
let niemiecki = new Intl.NumberFormat("de-DE");
niemiecki.format(9874563.21)

W powyższym przykładzie posługujemy się tagiem językowym w formacie BCP 47. BCP 47 to numer standardu. Pod tym linkiem znajdziecie bardzo długi i nieciekawy opis tego standardu, jednak pozwoli wam w pełni zrozumieć formatowanie liczb. Bardzo to uproszczę mówiąc, że pierwszy człon to język, drugi to lokalizacja (na przykład 'DE' to Niemcy, ale po niemiecku mówi się w wielu krajach.

Na przykład: 'de-DE' to niemiecki, 'de-AT' to niemiecki z lokalizacją w Austrii.

Trzeci człon występuje tylko czasami i jest kodem regionu albo wersji języka np.: chiński uproszczony.

Jeśli tag językowy nie jest uzupełniony to funkcja wyciągnie wartość domyślną, bowiem jest to wartość opcjonalna.

Dalej robi się dużo ciekawiej, bo możemy wstawić obiekt różnymi opcjami do których należą:

  • styl liczby (waluta,  zmiennoprzecinkowe, procent)
  • wyświetlanie waluty (symbol, słowo)
  • separatory (w którym miejscu przecinek, w którym odstęp albo kropka)
  • ilość miejsc przed i po przecinku, zarówno maksymalna jak i minimalna

Wszystko to co robiliśmy ręcznie, jest dostępne za pomocą jednej funkcji.

Spróbujmy coś pokombinować: Sto tysięcy złotych i dziewięćdziesiąt dziewięć groszy. Ale to ceny wyliczane statystycznie, więc grosze są do 4 miejsc po przecinku.

let number = 100000.993;
console.log(new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN', minimumFractionDigits: 4 }).format(number));

Powyższy kod zwróci "100 000,9930 zł".

Internationalization - formatowanie dat

Wisienką na torcie będzie formatowanie dat. Obiekt Date jest wszystkim znany, ale to co można z nim zrobić w namespace'ie Intl to już magia.

Zaznaczę, ale ominę lokalizację, która obejmuje kalendarz, system numeryczny i cykl godzinny. Skupmy się na opcjach, które to wszystko są w stanie napisać. Załóżmy, że chcemy na bloga dzień i miesiąc z godziną w polskiej strefie czasowej. To bardzo popularny format.

    let date = new Date();
    let options = {
        month: 'numeric',
        day: 'numeric',
        hour: 'numeric',
        minute: 'numeric',
        hour12: false,
        timeZone: 'Europe/Warsaw'
    }

    console.log(new Intl.DateTimeFormat('en-US', options).format(date))

Wynikiem tego będzie 9/22, 01:58. Nie bardzo, prawda?

    let date = new Date();
    let options = {
        day: 'numeric',
        month: 'long',
        hour: 'numeric',
        minute: 'numeric',
        hour12: false,
        timeZone: 'Europe/Warsaw'
    }

    console.log(new Intl.DateTimeFormat('en-US', options).format(date))

September 22, 02:02 oto on, zmieniliśmy miesiąc na słowny.

Podsumowanie

Na dzisiaj już wszystko jako że staram się trzymać liczbę znaków w rydzach. Mam nadzieje, że się podobało. Na chwilę przystopujemy z teorią, ale z pewnością pojawi się trzecia część ES6, a seria Mastering Javascript na pewno nie umrze :)

Kamila

Dziękuję za poświęcony czas, będzie mi bardzo miło jak zostawisz komentarz :)