Przyszłość ES6

> Dodaj do ulubionych

ES6 bez tajemnic to cykl artykułów poświęconych nowym składnikom języka JavaScript, które pojawiły się w 6. edycji standardu ECMAScript, w skrócie ES6.

Poprzedni artykuł na temat modułów zakończył naszą wielotygodniową podróż szlakiem najważniejszych nowości w ES6.

W tym artykule omówię garść innych nowych elementów, których nie miałem okazji wyczerpująco przedstawić. Potraktuj to jak zwiedzanie zamku, w którego komnatach mieszczą się wszystkie struktury języka. Zobaczymy intrygujące wnętrza, wejdziemy do tajemnej wieży, a może nawet zajrzymy do podziemnej pieczary. Jeśli nie czytałeś pozostałych artykułów z tej serii, to możesz się z nimi zapoznać tutaj. Naprawdę warto – rozpoczynanie lektury od tej części nie jest najlepszym pomysłem.

zrzut ekranu z jakiejś mrocznej gry
Po lewej widzimy tablice typowane…

Jeszcze jedno: wiele z przedstawionych poniżej składników nie doczekało się jeszcze implementacji na szeroką skalę.

OK. Możemy zaczynać.

Elementy funkcjonalności, z których mogłeś już korzystać

W ES6 ustandaryzowane zostały pewne elementy, które zdefiniowano już w innych specyfikacjach, bądź które były zaimplementowane na szeroką skalę, lecz nieopisane w żadnym standardzie.

  • Tablice typowane oraz obiekty ArrayBuffer i DataView. Wszystkie te trzy elementy znajdują się w standardzie WebGL, jednak wykorzystuje się je także w innych interfejsach API, np. Canvas, APIWeb Audio czy WebRTC. Są zawsze przydatne w przetwarzaniu surowych danych binarnych i liczbowych o dużej objętości.

    Jeśli na przykład w kontekście rysunku kanwy brakuje potrzebnego nam rozwiązania i jesteśmy zdeterminowani, by z niego korzystać, to możemy zaimplementować je samodzielne:

    var context = canvas.getContext("2d");
    var image = context.getImageData(0, 0, canvas.width, canvas.height);
    var pixels = image.data;  // obiekt Uint8ClampedArray
    // ... Twój kod!
    // ... Operujemy na surowych bitach ze zmiennej pixels
    // ... i zapisujemy je z powrotem w kanwie:
    context.putImageData(image, 0, 0);

    W wyniku standaryzacji tablice typowane zyskały metody, mi.n. .slice(), .map() i .filter().

  • Obietnice. Napisać zaledwie jeden akapit o obietnicach to tak jak zadowolić się jednym czipsem. Pomijając już jak trudna to sztuka, nie ma to zbyt wielkiego sensu. Obietnice są jednym z fundamentów programowania asynchronicznego w JS. Reprezentują wartości, które mają być dostępne później. Kiedy więc np. wywołamy metodę fetch(), wykonanie skryptu nie zostanie zablokowane, lecz natychmiast zwrócony będzie obiekt Promise. Metoda jest wykonywana w tle do czasu otrzymania odpowiedzi, kiedy to następuje wywołanie zwrotne. Obietnice są lepsze od samych wywołań zwrotnych, ponieważ łatwo łączą się w łańcuchy i są wartościami pierwszej klasy mającymi ciekawe metody. Ponadto dzięki obietnicom do obsługi błędów nie trzeba używać tak wielu powtarzalnych fragmentów kodu. Z obietnic można korzystać w przeglądarkach za pomocą wypełniacza. Osoby, które nie wiedzą jeszcze wszystkiego na temat obietnic zachęcam do lektury wyczerpującego artykułu Jake’a Archibalda.
  • Funkcje w zasięgu blokowym. Nie powinieneś używać tego elementu funkcjonalności, ale możliwe, że już z niego korzystałeś. Być może nieświadomie.

    W wersjach ES1ES5 tego rodzaju kod był teoretycznie niedozwolony:

    if (temperature > 100) {
      function chill() {
        return fan.switchOn().then(obtainLemonade);
      }
      chill();
    }

    Deklaracje funkcji wewnątrz bloku if były rzekomo zabronione. Można z nich było korzystać jedynie w najwyższym zakresie lub wewnątrz najwyższego bloku funkcji.

    Jednak rozwiązanie to i tak funkcjonowało we wszystkich głównych przeglądarkach. A przynajmniej do pewnego stopnia – zachowanie funkcji w zasięgu blokowym różniło się w zależności od przeglądarki, przez co nie można było mówić o pełnej zgodności.

    Niemniej funkcje jakoś działały i wiele przeglądarek korzysta z nich do dziś.

    Na szczęście w ES6 ten element funkcjonalności doczekał się standaryzacji. W opisanym przypadku funkcja jest windowana na początek wyższego bloku.

    Niestety w Firefoksie i Safari nowy standard nie został jeszcze zaimplementowany. Na tę chwilę możemy więc skorzystać z wyrażenia funkcyjnego:

    if (temperature > 100) {
      var chill = function () {    
        return fan.switchOn().then(obtainLemonade);
      };
      chill();
    }

    Funkcje w zasięgu blokowym nie zostały ustandaryzowane przed laty tylko dlatego, że ograniczenia związane z zapewnieniem zgodności wstecznej były niezwykle skomplikowane. Nikt nie przypuszczał, że da się je obejść. W ES6 rozwiązano to poprzez wprowadzenie bardzo dziwnej reguły, która ma zastosowanie tylko w kodzie uruchamianym w trybie bez ograniczeń. Nie mogę tego teraz objaśnić, jednak zaufaj mi i korzystaj z trybu ścisłego.

  • Nazwy funkcji. Wszystkie główne silniki JS od dawna obsługiwały też niestandardową własność .name dla funkcji mających nazwy. W ES6 zostało to ustandaryzowane i ulepszone. Teraz niektóre funkcje, które do tej pory traktowane były jako nienazwane otrzymują sensowną własność .name.
    > var lessThan = function (a, b) { return a < b; };
    > lessThan.name
        "lessThan"

    Jeśli zaś chodzi o inne funkcje, np. wywołania zwrotne pełniące rolę argumentów metod .then, specyfikacja wciąż nie określa ich nazwy. Własność fn.name jest więc pustym łańcuchem.

Przyjemne drobiazgi

  • Object.assign(cel, ...źródła). Nowa funkcja w bibliotece standardowej, podobna do .extend() z biblioteki Underscore.
  • Operator rozszczepiania w wywołaniu funkcji. Wbrew temu co może sugerować nazwa, operator rozszczepiania stanowi bardzo przyjemny element funkcjonalności JS.

    Jakiś czas temu omawialiśmy parametry resztowe. Pozwalają one funkcjom na przyjęcie dowolnej liczby argumentów i są bardziej cywilizowanym rozwiązaniem od nieprzewidywalnego, nieporęcznego obiektu arguments.

    function log(...stuff) {  // stuff to parametr resztowy
      var rendered = stuff.map(renderStuff); // prawdziwa tablica
      $("#log").add($(rendered));
    }

    Nie powiedzieliśmy jednak o analogicznej składni do przekazywania dowolnej liczby argumentów do funkcji, która stanowi bardziej cywilizowaną alternatywę dla metody fn.apply():

    // wyświetl wszystkie wartości przechowywane w tablicy
    log(...mojaTablica);

    Składnię tę można oczywiście zastosować dla dowolnego iterowalnego obiektu, zatem możliwe jest wyświetlenie wszystkich elementów zbioru poprzez komendę log(...mójZbiór).

    W przeciwieństwie do parametrów resztowych, wielokrotne użycie operatora rozszczepiania w obrębie pojedynczej listy argumentów jest uzasadnione:

    // najpierw kicks, potem trids
    log("Kicks:", ...kicks, "Trids:", ...trids);

    Operator rozszczepiania przydaje się też, jeśli chcemy zmniejszyć tablicę składającą się z innych tablic:

    > var małeTablice = [[], ["jeden"], ["dwa", "dwaa"]];
    > var jednaDużaTablica = [].concat(...małeTablice);
    > jednaDużaTablica
        ["jeden", "dwa", "dwaa"]

    …jednak niewykluczone, że tylko ja miałem taką palącą potrzebę. Jeśli faktycznie jestem wyjątkiem, to przez Haskella.

  • Operator rozszczepiania do tworzenia tablic. Jakiś czas temu mówiliśmy także o zastosowaniu parametrów resztowych w destrukturyzacji. Za ich pomocą można wydzielić z tablicy dowolną liczbę elementów:
    > var [głowa, ...ogon] = [1, 2, 3, 4];
    > głowa
        1
    > ogon
        [2, 3, 4]

    I wiecie co? Istnieje analogiczna składnia umożliwiająca dodanie dowolnej liczby elementów tablicy:

    > var połączone = [głowa, ...ogon];
    > połączone
        [1, 2, 3, 4]

    Parametr ten podlega tym samym regułom co operator rozszczepiania w wywołaniach funkcji: można go wielokrotnie wykorzystać w obrębie tej samej tablicy itd.

  • Porządna rekurencja ogonowa. To zbyt niezwykła nowość, bym mógł ją omówić w tym miejscu.

    By zrozumieć na czym cała rzecz polega, polecam zacząć od przeczytania pierwszej strony podręcznika Structure and Interpretation of Computer Programs. Jeśli jej lektura przypadnie ci do gustu, to czytaj dalej. Wyjaśnienie rekurencji ogonowej znajdziesz w sekcji 1.2.1, „Linear Recursion and Iteration”. W ES6 implementacje muszą być „rekurencyjne ogonowo”, zgodnie z zamieszczoną w standardzie definicją.

    Ten element funkcjonalności nie został jeszcze zaimplementowany w żadnym z głównych silników JS. To trudne zadanie, ale wszystko w swoim czasie.

Tekst

  • Wyższa wersja Unicode. Zgodnie ze specyfikacją ES5 implementacje miały obsługiwać wszystkie znaki Unicode przynajmniej z wersji 3.0. Implementacje ES6 muszą natomiast obsługiwać co najmniej Unicode 5.1.0. Można więc teraz w nazwach funkcji stosować znaki z pisma linearnego B!

    Użycie znaków pisma linearnego A wciąż może być ryzykowne, ponieważ znalazły się one dopiero w Unicode w wersji 7.0. Ponadto obsługa kodu zapisanego pismem, które nigdy nie zostało odczytane mogłoby być trudne.

    (Nawet w tych silnikach JavaScript, które obsługują emoji dodane w Unicode 6.1 nie można użyć ? jako nazwy zmiennej. Z jakiegoś powodu konsorcjum Unicode postanowiło nie klasyfikować emoji jako dozwolonego znaku identyfikatora ?).

  • Długie sekwencje specjalne Unicode. ES6, podobnie jak wcześniejsze wersje JS, obsługuje czterocyfrowe sekwencje specjalne Unicode. Wyglądają one np. tak: \u212A i są świetne. Można umieszczać je w łańcuchach, a nawet – jeśli miałbyś ochotę trochę popsocić i wiesz, że kod nie będzie poddany recenzji – w nazwach zmiennych. Mały problem pojawi się wtedy, jeśli chcielibyśmy użyć np. znaku U+13021, egipskiego hieroglifu przedstawiającego człowieka stojącego na głowie. Liczba 13021 ma pięć cyfr, a pięć to więcej niż cztery.

    W ES5 w takim przypadku trzeba było posłużyć się dwoma sekwencjami specjalnymi, parą surogatów UTF-16. Było to rozwiązanie wręcz średniowieczne. Niczym epoka renesansu, era ES6 przynosi jednak wielkie zmiany: teraz można użyć zapisu \u{13021}.

  • Lepsza obsługa znaków spoza podstawowej przestrzeni wielojęzycznej (BMP). Metody .toUpperCase() i .toLowerCase() działają teraz na łańcuchach napisanych w alfabecie Deseret!

    Analogicznie funkcja String.fromCodePoint(...współrzędnaZnaku) jest bardzo podobna do starszej String.fromCharCode(...jednostkiKodowe) , lecz obsługuje współrzędne kodowe znaków spoza BMP.

  • Wyrażenia regularne Unicode. Wyrażenia regularne w ES6 obsługują nową flagę u, która sprawia, że wyrażenie regularne traktuje znaki spoza podstawowej przestrzeni wielojęzycznej jako pojedyncze znaki, a nie dwie oddzielne jednostki kodowe. Przykładowo wyrażenie /./ bez flagi u odpowiada tylko połowie znaku ? ., natomiast /./u odpowiada całości.

    Ustawienie flagi u na wyrażeniu regularnym pozwala także na dokładniejsze dopasowania znaków Unicode bez rozróżnienia wielkich i małych liter oraz długie sekwencje specjalne Unicode. Wszystkie informacje na ten temat można znaleźć w szczegółowym artykule Mathiasa Bynensa.

  • Lepkie wyrażenia regularne. Niezwiązaną z Unicode nowością jest flaga y, znana także jako flaga „przylepka” (ang. sticky). Lepkie wyrażenie regularne szuka jedynie dopasowań zaczynających się w dokładnie tym punkcie, który podaje własność .lastIndex. W przypadku braku dopasowania, wyrażenie „przylepka” nie przegląda pozostałej części łańcucha, lecz od razu zwraca null.
  • Oficjalna specyfikacja internacjonalizacji. Implementacje ES6 zawierające jakiekolwiek elementy internacjonalizacji muszą być zgodne ze standardem ECMA-402, specyfikacją ECMAScript 2015 dotyczącą API do obsługi internacjonalizacji API. Ten oddzielny dokument określa obiekt Intl. Przeglądarki Firefox, Chrome i IE11+ już go w pełni obsługują, podobnie jak środowisko Node 0.12.

Liczby

  • Literały liczb dwójkowych i ósemkowych. Jeśli potrzebujesz fikuśnego sposobu na zapisanie liczby 8 675 309 i rozwiązanie 0x845fed nie spełnia twoich oczekiwań, możesz teraz wypróbować zapis 0o41057755 (liczba ósemkowa) lub 0b100001000101111111101101 (liczba dwójkowa).

    Łańcuchy w tym formacie można też teraz przekazać funkcji Number(łańcuch): Number("0b101010") zwróci 42.

    (Dla przypomnienia: metody liczba.toString(podstawa) i parseInt(łańcuch, podstawa) to pierwotne sposoby na zamianę liczb z systemu i na system o dowolnej podstawie).

  • Nowe funkcje i stałe Number. To dosyć niszowe składniki. Jeśli cię interesują, możesz przejrzeć standard samodzielnie, zaczynając od własności Number.EPSILON.

    Chyba najciekawszą nowością z tego zestawu jest „bezpieczny zakres” liczb całkowitych, od −(253 − 1) do +(253 − 1) włącznie. Ten specjalny zakres był częścią JS od samego początku. Każdej liczbie, która się w nim znajduje można dokładnie przyporządkować liczbę JS, podobnie jak innym sąsiadującym z nią liczbom. Krótko mówiąc, to zakres, w którym operatory ++ i -- działają tak jak należy. Znajdujące się poza tym zakresem liczby nieparzyste nie mają swojej reprezentacji jako 64-bitowe liczby zmiennoprzecinkowe, dlatego inkrementacja i dekrementacja liczb, które taką reprezentację mają (wszystkie z nich są parzyste) nie daje poprawnego wyniku. Jeśli ma to znaczenie dla twojego kodu, możesz skorzystać z nowych stałych Number.MIN_SAFE_INTEGER i Number.MAX_SAFE_INTEGER oraz predykatu Number.isSafeInteger(n) .

  • Nowe funkcje Math. W ES6 dodano m.in. hiperboliczne funkcje trygonometryczne i ich odwrotności, funkcję Math.cbrt(x) do obliczania pierwiastków sześciennych, Math.hypot(x, y) do obliczania długości przeciwprostokątnej trójkąta prostokątnego, Math.log2(x) i Math.log10(x) do obliczania logarytmów o tej samej podstawie, a także funkcję Math.clz32(x) ułatwiającą obliczanie logarytmów liczb całkowitych.

    Math.sign(x) sprawdza znak liczby.

    Ponadto ES6 wprowadza nową funkcję Math.imul(x, y) , która wykonuje mnożenie modulo 232 ze znakiem. To bardzo nietypowe działanie… chyba że musimy znaleźć sposób, by obsłużyć 64-bitowe liczby całkowite bądź duże liczby całkowite, których w JS nie ma. W takich sytuacjach funkcja ta bardzo się przydaje i pomaga kompilatorom. Korzysta z niej kompilator Emscripten, by zaimplementować w JS mnożenie 64-bitowych liczb całkowitych.

    Podobnie funkcja Math.fround(x) ma zastosowanie w kompilatorach, które muszą obsługiwać 32-bitowe liczby zmiennoprzecinkowe.

To już koniec

Czy to wszystko, jeśli chodzi o ES6?

Niezupełnie. Nie wspomniałem nawet o obiekcie, który stanowi wspólny prototyp wszystkich wbudowanych iteratorów, ściśle tajnym konstruktorze GeneratorFunction, metodzie Object.is(v1, v2) , o tym jak Symbol.species wspiera tworzenie podklas klas wbudowanych takich jak Array czy Promise, ani jak dokładnie określono funkcjonowanie wielu obiektów globalnych, co do tej pory nie były ustandaryzowane.

Na pewno też kilka rzeczy przeoczyłem.

Jeśli jednak śledziliście wszystkie artykuły, to możecie się domyślać w jakim kierunku zmierza JS. Wiecie, że możecie używać składników ES6 już dziś, a jeśli się na to zdecydujecie, to wybierzecie lepszą wersję języka.

Jakiś czas temu Josh Mock powiedział mi, że ostatnio zdarzyło mu się skorzystać, praktycznie nieświadomie, z ośmiu różnych elementów ES6 w kodzie o objętości ok. 50 wierszy. Były to moduły, klasy, argumenty domyślne, zbiory, słowniki, łańcuchy szablonowe, funkcje strzałkowe i słowo kluczowe let (w jego kodzie zabrakło pętli for-of).

Mam podobne doświadczenia. Nowości z ES6 tworzą zgrany zestaw i mają wpływ na niemalże każdy wiersz kodu.

W międzyczasie trwają gorączkowe prace nad implementacją i optymalizacją omówionych przez nas składników we wszystkich silnikach JS. Gdy dobiegną końca, to i język będzie można uznać za ukończony. Nigdy już nie będziemy musieli nic zmieniać. A ja będę musiał znaleźć sobie inne zajęcie.

Oczywiście żartuję. Pojawiają się już propozycje nowości do wprowadzenia w ES7. Są to np.:

  • Operator potęgowania. Działanie 2 ** 8 zwróci 256. Składnik zaimplementowany w Firefoksie Nightly.
  • Array.prototype.includes(wartość). Zwraca wartość true, jeśli tablica zawiera daną wartość. Składnik zaimplementowany w Firefoksie Nightly. Można z niego korzystać za pomocą wypełniacza.
  • SIMD. Udostępnia 128-bitowe instrukcje SIMD nowoczesnych procesorów. Instrukcje te wykonują działania arytmetyczne jednocześnie na 2, 4 albo 8 sąsiadujących ze sobą elementach tablicy. Może to znacznie przyspieszyć wiele algorytmów służących do przetwarzania obrazów, strumieniowego przesyłania plików audio i wideo, algorytmów gier, algorytmów kryptograficznych i innych. Rozwiązanie bardzo niskopoziomowe, a jednocześnie potężne. Zaimplementowane w Firefoksie Nightly. Można z niego korzystać za pomocą wypełniacza.
  • Funkcje asynchroniczne. Zasugerowaliśmy je w artykule na temat generatorów. Funkcje asynchroniczne przypominają generatory, lecz są zaprojektowane specjalnie pod kątem programowania asynchronicznego. Wywołany generator zwraca iterator, natomiast funkcja asynchroniczna zwraca obietnicę. Generatory korzystają ze słowa kluczowego yield, by wstrzymać swoje działanie i wygenerować wartość. Z kolei funkcje asynchroniczne są wstrzymywane za pomocą słowa kluczowego await i czekają na obietnicę.

    Trudno opisać je w kilku zdaniach, jednak funkcje te będą sztandarową nowością w ES7.

  • Obiekty typowane. To kontynuacja tablic typowanych. Tablice typowane zawierają typowane elementy. Obiekt typowany ma natomiast własności typowane.
    // Utwórz nowy typ strukturalny. Każdy obiekt Point ma dwa pola
    // o nazwie x oraz y.
    var Point = new TypedObject.StructType({
      x: TypedObject.int32,
      y: TypedObject.int32
    });
    
    // Teraz utwórz egzemplarz tego typu.
    var p = new Point({x: 800, y: 600});
    console.log(p.x); // 800

    Powyższe rozwiązanie można by zastosować jedynie w celu polepszenia wydajności. Podobnie jak tablice typowane, obiekty typowane dają programistom korzyści płynące z kontroli typów (zużycie małej ilości pamięci i szybkość). Ponadto, w przeciwieństwie do języków typowanych zupełnie statycznie, możliwości te można zastosować do poszczególnych obiektów.

    Ponadto obiekty typowane mogą być dobrym celem kompilacji JS.

    Zaimplementowano je już w Firefoksie Nightly.

  • Dekoratory klas i własności. Dekoratory to znaczniki dodawane do własności, klas i metod. Oto ilustrujący je przykład:
    import debug from "jsdebug";
    
    class Person {
      @debug.logWhenCalled
      hasRoundHead(assert) {
        return this.head instanceof Spheroid;
      }
      ...
    }

    Dekoratorem jest tu fragment @debug.logWhenCalled. Można sobie łatwo wyobrazić, jak działa na metodę.

    Jego działanie zostało szczegółowo objaśnione i zilustrowane przykładami w tej propozycji.

Muszę wspomnieć o jeszcze jednej ekscytującej rzeczy. Nie jest to element samego języka.

TC39, komisja standaryzacyjna ECMAScriptu zmierza ku częstszym aktualizacjom i upublicznieniu procesu tworzenia standardu. ES5 i ES6 dzieliło sześć lat. Zgodnie z zamierzeniami komisji ES7 ma zostać wydany zaledwie 12 miesięcy po publikacji ES6. Kolejne wersje standardu również mają być publikowane co 12 miesięcy. Niektóre z wymienionych składników będą do tego czasu gotowe i znajdą się w specyfikacji ES7. Te, których nie uda się w porę dokończyć, trafią do kolejnego standardu.

Świetnie się bawiłem, dzieląc się z wami tymi wszystkimi nowościami jakie przyniosło ES6. Jednocześnie cieszę się, że już prawdopodobnie nigdy nie otrzymamy tak dużego zestawu nowych składników na raz.

Dziękuję, że śledziliście serię artykułów ES6 bez tajemnic! Mam nadzieję, że przypadła wam do gustu. Zachęcamy do ciągłego odwiedzania naszej strony.

Autor: Jason Orendorff

Źródło: https://hacks.mozilla.org/2015/08/es6-in-depth-the-future/

Tłumaczenie: Joanna Liana

Treść tej strony jest dostępna na zasadach licencji CC BY-SA 3.0