ECMAScript 6 jest nową choć nie najnowszą (ECMAScript 7), wersją standardu ECMAScript i bazuje na ECMAScript 5.1 z 2009 r.
Od lipca 2014, ES6 nie jest poszerzany o nowe funkcjonalności. Po procesie publikacji, który rozpocznie się w marcu 2015, ES6 będzie ukończony w czerwcu 2015.
Obecnie nie ma dobrych powodów, dla których nie mielibyśmy zacząć używać ES6 już dziś. Jest to możliwe dzięki takim narzędziom jak Traceur od Google czy Babel (6to5.js).
W tym artykule mam zamiar pokazać Ci niektóre funkcjonalności ECMAScript 6 bazując na jednym z moich repozytoriów gdzie znajdziesz więcej przykładów. Nie odkryję Ameryki jeżeli stwierdzę, że najlepszą metodą na uczenie się nowych narzędzi (bibliotek, frameworków, języka itd.) jest pisanie testów i takiego podejścia staram się trzymać. Jest to główny powód dla którego kod w repozytorium nie jest tylko zbiorem przykładów, a również mini środowiskiem do transpilacji ES6 do ES5 (poprzez Traceur) oraz testów jednostkowych (Karma, Jasmine).
Pomimo tego, że Traceur to wspaniałe narzędzie to ma ono swoje ograniczenia np. brak obsługi Proxy. Jeżeli szukasz dokładniejszych informacji odnośnie tego jakie narzędzia wspierają jakie funkcjonalności dobrym punktem wyjścia będzie ECMAScript compatibility table.
Zwróć uwagę na to, że „ostateczne wsparcie” twojego środowiska jest sumą funkcjonalności wspieranych przez twoją przeglądarkę lub serwer oraz transpilator z jakiego korzystasz (jeżeli w ogóle). Przykładowo Traceur nie wspiera struktury WeekMap, ale Chrome radzi sobie z jej obsługą całkiem nieźle co oznacza, że z powodzeniem możesz wykorzystywać WeekMap w swoim kodzie. Ja przy tworzeniu przykładów korzystałem z Chrome i jeżeli również chcesz z niego korzystać nie zapomnij o przełączeniu flagi odpowiedzialnej za włączenie eksperymentalnych funkcjonalności JavaScript.
Let
Słowo kluczowe let jest podobne do dobrego, starego var. Różnica leży w zasięgu. Zasięg var jest globalny lub ograniczony poprzez funkcję co bez posiadania tej wiedzy może prowadzić do nieoczekiwanych bugów (np. zmienna stworzona w bloku instrukcji if będzie dostępna również poza nią). Let natomiast posiada zasięg ograniczony przez blok lub wyrażenie co, dla osób, które lepiej czują się w językach statycznie typowanych tj. Java, jest bardziej naturalnym zachowaniem.
1 2 3 4 5 6 7 8 9 10 11 12 13 | expect(() => { if (true) { var x = 1; } expect(x).toEqual(1); }).not.toThrowError(); expect(() => { if (true) { let x = 1; } expect(x).toEqual(1); }).toThrowError("x is not defined"); |
Stałe
Stałe jak zapewne się domyślasz nie mogą zostać nadpisane, ale mogą być modyfikowane – można edytować właściwości obiektu przypisanego do stałej. Jeżeli chodzi o zasięg to, podobnie jak w przypadku let, jest ograniczony do bloku czy wyrażenia.
1 2 3 4 5 6 7 8 9 10 11 | const x = 1; const y = {x: 1}; const z = {x: 1}; x = 2; // error y = {x: 2}; // error z.x = 2; // overwriting fails expect(x).toEqual(1); expect(y).toEqual({x: 1}); // modifying works, properties are not protected expect(z).toEqual({x: 2}); |
Arrow functions
Arrow functions (funkcje strzałki? bleh.) są skróconym zapisem anonimowej funkcji.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | let square = x => x * x; let triangleArea = (a, h) => a*h/2; let triangleHeron = (a, b, c) => { let p = (a + b + c)/2; return Math.sqrt(p*(p-a)*(p-b)*(p-c)); }; let objectify = x => ({ value: x }); expect(square(13)).toEqual(169); expect(triangleArea(4, 6)).toEqual(12); expect(triangleHeron(3, 4, 5)).toEqual(6); expect(objectify("foo")).toEqual({ value:"foo" }); |
Jeżeli znasz CoffeeScript to arrow functions działają tak samo jak funkcje stworzone za pomocą flat arrow. Poza minimalistyczną składnią arrow functions w odróżnieniu od funkcji stworzonej za pomocą słowa kluczowego function dzielą to samo this z zawierającym je kodem (nie ograniczają zasięgu).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | let person = { name: "Bob", belongings: ["Car", "PC"], getProperties: function () { let properties = []; this.belongings.forEach(function (thing) { properties.push(this.name + " has " + thing); }); return properties; }, getProperties2: function () { let properties = []; // arrows share this with surrounding code this.belongings.forEach((thing) => { properties.push(this.name + " has " + thing); }); return properties; } }; expect(() => person.getProperties()) .toThrow(new TypeError("Cannot read property 'name' of undefined")); expect(person.getProperties2()).toEqual(["Bob has Car", "Bob has PC"]); |
Domyślne parametry
Domyślne parametry pozwalają na przypisanie wartości w przypadku nie podania argumentu uwalniając nas od trochę podstępnego parameter = parameter || default.
1 2 3 4 5 6 7 | function f(list, indexA = 0, indexB = list.length) { return [list, indexA, indexB]; } expect(f([1, 2, 3])).toEqual([[1, 2, 3], 0, 3]); expect(f([1, 2, 3], 1)).toEqual([[1, 2, 3], 1, 3]); expect(f([1, 2, 3], 1, 2)).toEqual([[1, 2, 3], 1, 2]); |
Klasy
Czy Ci się to podoba czy nie (jak to jest w moim przypadku), klasy wprowadzone przez ES6, są tylko składniowym cukrem, którym możemy posypać prototypy. W ES6 specyfikacja odnośnie klasy jest naprawdę skromna i nie daje powodów (i możliwości) na porzucenie myślenia obiektowego w JavaScript w kategorii prototypów, przysłaniania właściwości itd. Jednak przyznaję, że extends wygląda przyjaźniej niż Child.prototype = new Parent().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Point { constructor(x = 0, y = 0) { this.x = x; this.y = y; } } class Circle extends Point { constructor(r, x, y) { super(x, y); this.r = r; } isPointIncluded(point) { if (point.constructor != Point) throw new Error("point must be an instance of Point"); return Math.pow(this.r, 2)+Math.pow(this.y, 2) >= Math.pow(this.x-point.x, 2)+Math.pow(this.y-point.y, 2); } } let c = new Circle(6, 2, 1); expect(c.isPointIncluded(new Point(2, 7))).toEqual(true); expect(c.isPointIncluded(new Point(3, -1))).toEqual(true); expect(c.isPointIncluded(new Point(6, 6))).toEqual(false); |
Destructing Assignement
Destructing assignement pozwalają na „wyciągnięcie” konkretnych wartości z tablicy lub obiektu i zapisanie ich do osobnych zmiennych.
1 2 3 4 5 6 7 8 9 10 11 12 | let [a, , [b, c]] = [1, 2, [3, 4]]; expect(a).toEqual(1); expect(b).toEqual(3); expect(c).toEqual(4); let {firstName, lastName: surname, info: {age, driver}} = {firstName: "Foo", lastName: "Bar", info: {age: 20, driver: true}}; expect(firstName).toEqual("Foo"); expect(surname).toEqual("Bar"); expect(age).toEqual(20); expect(driver).toEqual(true); |
Rest Parameters
Rest parameters jest popularnym wzorcem w wielu językach np. RUby, PHP (5.6+). Możemy przyjąć w funkcji wiele parametrów jako tablicę i pożegnać obiekt arguments.
1 2 3 4 5 6 7 8 9 10 | function buy(where, ...items) { return "I'm going to " + where + " to buy " + items.length + " items: " + items.slice(0, -1).join(", ") + " and " + items.slice(-1) + "."; } expect(buy("the mall", "jacket", "bag", "sweets", "headphones")) .toEqual("I'm going to the mall to buy 4 items: " + "jacket, bag, sweets and headphones."); |
Spread
Operator spread jest odwrotnością rest parameters. Pozwala nam rozbić tablicę na kolejne parametry funkcji.
1 2 3 4 5 6 7 | function send(what, where, toWhom) { return "I'm sending " + what + " to " + toWhom + " who is in " + where + "."; } expect(send(...["the letter", "Poland", "Mike"])) .toEqual("I'm sending the letter to Mike who is in Poland."); |
Symbole
Symbole to nowy, niemutowalny i unikalny typ. Nie są one ani obiektem ani typem prostym. Mogą być wykorzystane jako identyfikatory właściwości obiektów, w tym w WeakMap. Opcjonalna nazwa może być wykorzystana w celu debugowania i przez metodę Symbol.for
1 2 3 4 5 6 7 8 | let s = Symbol("foo"); expect(s).not.toEqual(Symbol("foo")); expect(typeof s).toEqual("symbol"); let s2 = Symbol.for("foo"); expect(s).not.toEqual(s2); expect(s2).toEqual(Symbol.for("foo")); expect(Symbol.keyFor(s2)).toEqual("foo"); |
Może się wydawać (mi tak się wydawało), że symbole są świetnym sposobem na symulowanie prywatnych właściwości (przez unikalne identyfikatory, niedostępne poza funkcją konstruktora). I wiecie co? Nie są.
1 2 3 4 5 6 7 8 9 10 11 | function MyClass(secretData) { let s = Symbol("secretData symbol"); this[s] = secretData; } let obj = new MyClass("secret"); expect(obj["secret"]).toBeUndefined(); expect(Object.getOwnPropertySymbols(obj)).toEqual(jasmine.any(Array)); expect(obj[Object.getOwnPropertySymbols(obj)[0]]) .toEqual("secret"); |
Istnieje również coś takiego jak dobrze znane symbole, które mogą być użyte do świetnych rzeczy tj. iteratory. O iteratorach porozmawiamy za chwilę.
Rozszerzone literały obiektu
O ile sam nagłówek pewnie jest niezrozumiały o tyle kryje się pod tym prosta koncepcja. Właściwości obiektu mogą zostać wyliczone, skrócone lub dać dostęp do __proto__. Również zyskujemy możliwość wywołania przysłanianej metody z prototypu ( super()) jednak nie jest to możliwe przy wykorzystaniu Traceur i Chrome 40 (aktualnej wersji w momencie gdy po piszę).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function greet(name) { return "Hello " + name; } let x = 2; let obj = { [x*2]: "Computed Property Name", __proto__: { hi: function () { return "Hi!" }, by: function () { return "By!" } }, greet }; expect(obj[4]).toEqual("Computed Property Name"); expect(obj.hi()).toEqual("Hi!"); expect(obj.by()).toEqual("By!"); expect(obj.greet("Bob")).toEqual("Hello Bob"); |
Iteratory
Iterator to obiekty zwracające wartości, do których dostęp możemy uzyskać m. in. za pomocą pętli for..of. Iteratory są deklarowane z wykorzystaniem Symbol.iterator i powinny zwracać obiekt zawierający metodę next. Iteratory są podobne do generatorów jednak jawnie zwracają obiekt, który zawiera dwie właściwości. Właściwość done określa czy iteracja powinna zostać zakończona, a właściwość value to wartość jaka zostanie nadana zmiennej podczas iteracji.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function fibonacci(i) { return { [Symbol.iterator]() { let pre = -1, cur = 1; return { next() { [pre, cur] = [cur, pre + cur]; return {done: !(i--), value: cur}; } } } } } let fib = []; for (let n of fibonacci(10)) { fib.push(n); } expect(fib).toEqual([0, 1, 1, 2, 3, 5, 8, 13, 21, 34]); |
Generatory
Generatory to bardzo potężne narzędzie w rękach programisty. Generatory są deklarowane bardzo podobnie jak zwyczajne funkcje z tą różnicą, że używamy słowa kluczowego function*, a sam generator zamiast zwracać jawnie wartość ( return), będzie podawać wartość więcej niż raz za pomocą yield.
1 2 3 4 5 6 7 8 9 10 11 | function* foo() { let i = 0; yield ++i; yield ++i; yield ++i; } let seq = foo(); expect(seq.next().value).toEqual(1); expect(seq.next().value).toEqual(2); expect(seq.next().value).toEqual(3); |
Istnieje również yield* który powinien „podać” inny generator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function* flatten(t, n = 0) { if (t[n]) { if (Array.isArray(t[n])) yield* flatten(t[n]) else yield t[n]; yield* flatten(t, n + 1); } } let nums = []; for (let n of flatten([10, 11, 12, [13, 14, [15, 16]], 17])) { nums.push(n); } expect(nums).toEqual([10, 11, 12, 13, 14, 15, 16, 17]); |
Nie napisałem jeszcze, że metoda next przyjmuje argument, który zostanie zwrócony przez yield.
1 2 3 4 5 6 7 8 9 | // The idea comes from http://youtu.be/s-BwEk-Y4kg?t=14m42s function* powGenerator() { return Math.pow(yield "a", yield "b"); } let g = powGenerator(); expect(g.next().value).toEqual("a"); expect(g.next(10).value).toEqual("b"); expect(g.next(2).value).toEqual(100); |
Ten artykuł jest przeglądem wielu funkcjonalności ES6, wiec nie ma w nim miejsca na szczegółowe zgłębianie każdej z nich. Jeżeli chcesz wiedzieć więcej o generatorach to sprawdź serię wpisów o generatorach w ES6 autorstwa Davida Walsha Kylea Simpsona.
Literały Numeryczne
Dzięki numerycznym literałom praca z liczbami binarnymi i w zapisie ósemkowym staje się banalna.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | expect([ 0b111, 0b11110000, 0b00001111 ]).toEqual([ 7, 240, 15 ]); expect([ 0o7, 0o360, 0o17 ]).toEqual([ 7, 240, 15 ]);<span style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; background-color: #ffffff;"> </span> |
Literały Szablonów
Literały Szablonów, o których mowa nie mają nic wspólnego web components. Konstrukcja będąca w wielu językach od dawna do JavaScriptu trafia w 2015 roku. Jak to mówią, lepiej późno niż wcale. Wykorzystując trochę inny, bo za pomocą
1 |
1 2 3 4 5 6 | , a nie apostrofów, sposób stworzenia łańcucha tekstowego zyskujemy możliwość umieszczenia w nim wartości zmiennych bez konieczności wykonywania jawnej konkatenacji. [crayon-67418d338f764390615060 lang="default" decode="true" ]let name = "Foo"; let surname = "Bar"; expect([/crayon]{name} ${surname}`).toEqual("Foo Bar"); |
Co więcej literał taki może otrzymać tag, który jest funkcją, a ta może go zmodyfikować.
Promises
Promises wykorzystywane przez wszystkich na długo przed przyklepaniem specyfikacji i są (w sumie to były) dużym krokiem naprzód w kwestii obsługi asynchronicznych zadań szczególnie gdy te zależały od siebie (pobierz klucz, pobierz token, użyj tokenu itd.). Istnieje wiele implementacji tego wzorca np. $q w AngularJS, który bazuje na Q Krisa Kowala czy rsvp.js.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function promiseMaker(condition, timeout = 2000) { return new Promise((resolve, reject) => { setTimeout(() => { if (condition) { resolve("Success!"); } else { reject(new Error("Something went wrong!")); } }, timeout); }); } promiseMaker(true) .then((data) => { expect(data).toEqual("Success!"); }); promiseMaker(false, 3000) .catch((err) => { expect(err).toEqual(new Error("Something went wrong!")); }); |
Więcej przykładów działania promises znajdziesz w promices.js w repozytorium.
Modules
Moduły pozwalają na wygodne korzystanie z zależności dzięki asynchronicznemu ładowaniu i jawnemu eksportowi. Możemy wtedy załadować cały moduł albo zaimportować wybrane eksporty.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // modules/math.js export function sum(x, y) { return x + y; } export var pi = 3.141593; // modules/person.js export var name = "Foo"; export var surname = "Bar"; // modules.js import * as mathematics from "modules/math"; import {name, surname} from "modules/person"; expect(mathematics).toEqual(jasmine.any(Object)); expect(mathematics.sum(2, 3)).toEqual(5); expect(mathematics.pi).toEqual(3.141593); expect(name).toEqual("Foo"); expect(surname).toEqual("Bar"); |
Ładowanie modułów działa zarówno po stronie serwera (to raczej nie jest zaskoczenie) jak i po stronie klienta.
1 2 3 4 5 6 7 8 | <script> System.import('modules/math').then((m) => { expect("2π = " + m.sum(m.pi, m.pi)).toEqual("2π = 6.283186"); }); System.import('modules/person').then((m) => { expect("I'm " + m.name + " " + m.surname).toEqual("I'm Foo Bar"); }); </script> |
Jeżeli interesują cię moduły to warto zainteresować się RequireJS,Browserify i jspm.io.
Map, Set, WeakMap i WeakSet
Map, Set, WeakMap i WeakSet są nowymi strukturami danych. Ich odpowiedniki znamy dobrze z Javy.
Map jest prostą strukturą z relacją klucz-wartość.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | let m = new Map([["name", "Foo"], ["surname","Bar"]]); m.set("age", 10).set("age", 20).set(false, "Foo"); expect(m.size).toEqual(4); expect(m.has("name")).toEqual(true); expect(m.has(false)).toEqual(true); expect(m.has("address")).toEqual(false); let mapIter = m.entries(); expect(mapIter.next().value).toEqual(["name", "Foo"]); expect(mapIter.next().value).toEqual(["surname", "Bar"]); expect(mapIter.next().value).toEqual(["age", 20]); expect(mapIter.next().value).toEqual([false, "Foo"]); expect(mapIter.next().value).toBeUndefined(); expect(m.delete("name")).toEqual(true); expect(m.has("name")).toEqual(false); expect(m.size).toEqual(3); m.clear(); expect(m.size).toEqual(0); |
Set przechowuje unikalne wartości dowolnego typu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | let s = new Set(["Foo", "Bar"]); s.add(false).add(123).add("Bar"); expect(s.size).toEqual(4); expect(s.has("Bar")).toBe(true); expect(s.has(123)).toBe(true); expect(s.has(true)).toBe(false); let setIter2 = s.values(); expect(setIter2.next().value).toEqual("Foo"); expect(setIter2.next().value).toEqual("Bar"); expect(setIter2.next().value).toEqual(false); expect(setIter2.next().value).toEqual(123); expect(setIter2.next().value).toBeUndefined(); expect(s.delete("Bar")).toEqual(true); expect(s.has("Bar")).toEqual(false); expect(s.size).toEqual(3); s.clear(); expect(s.size).toEqual(0); |
WeakMap jest kolekcją par klucz-wartość. Klucze są obiektami, a wartości mogą być dowolnego typu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | let wm = new WeakMap(), o1 = {}, o2 = function () {}, o3 = Symbol("foo"), o4 = window; wm.set(o1, 123); wm.set(o2, "FooBar"); wm.set(o3, undefined); wm.set(1, "Baz"); // Invalid value used as weak map key expect(wm.get(o1)).toEqual(123); expect(wm.get(o2)).toEqual("FooBar"); expect(wm.get(o3)).toBeUndefined(); expect(wm.get(o4)).toBeUndefined(); expect(wm.has(o1)).toEqual(true); expect(wm.has(o2)).toEqual(true); expect(wm.has(o3)).toEqual(true); expect(wm.has(o4)).toEqual(false); wm.delete(o1); expect(wm.has(o1)).toEqual(false); |
WeakSet jest najprostszą strukturą ze wszystkich. Przechowuje luźno powiązane obiekty.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | let ws = new WeakSet(), o1 = {}, o2 = function () {}, o3 = window; ws.add(o1); ws.add(o2); expect(ws.has(o1)).toEqual(true); expect(ws.has(o2)).toEqual(true); expect(ws.has(o3)).toEqual(false); ws.delete(o1); ws.delete(o2); expect(ws.has(o1)).toEqual(false); expect(ws.has(o2)).toEqual(false); |
Choć, a nie chodź… Pozdrowienia dla „korekty”
Cóż, pisanie w podróży mi nie służy. Dzięki.
Brawo za czas reakcji :)
Robim co możem :)
Chłopak tak się postarał, zarąbisty artykuł, a ty tylko się czepiłeś jakiegoś piep$#%ego 'chodź’ :)
>Obecnie nie ma dobrych powodów, dla których nie mielibyśmy zacząć używać ES6 już dziś.
Jedyne powody do pisania czegoś w ES6 to Proxy, subklasowanie natywnych obiektów oraz string interpolation. Żadnej z tych rzeczy żaden konwerter do ES5 nie potrafi. Jedyna przeglądarka mająca to wszystko to nowy lisek, Chrome brakuje obsługa Proxy. A wszystko skrzętnie poukrywane za flagami (polecam spojrzeć ile flag potrzebuje io.js). Pisanie klas tylko po to, żeby je przerobić na prototypy IMO mija się z celem. A już całkowicie nie rozumiem tworzenia całych języków (atScript), które są supersetami ES6 i… są kompilowane do ES5, żeby działać. Dla mnie bezsens.
Przy stałych i let pominięto najważniejsze rzeczy, czyli 'temporal dead zone’ oraz brak hoistingu. Dzięki temu mamy de facto całkowicie dwa odmienne obiegi zmiennych, które zachowują się całkowicie inaczej.
>Stałe jak zapewne się domyślasz nie mogą zostać nadpisane, ale mogą być modyfikowane – można edytować właściwości obiektu przypisanego do stałej.
Wypadałoby przy tym zaznaczyć, że po prostu wynika to z tego, że w JS wartości prymitywne są niemutowalne (zatem porównywane przez wartość), a obiekty posiadają tzw. tożsamość. Nie modyfikujemy samej tożsamości obiektu, jedynie modyfikujemy jego własności.
Przy arrow functions wypada zwrócić uwagę, że nie dziedziczą po Function.prototype (a przynajmniej nie w pełni)
>trochę podstępnego parameter = parameter || default
Czemu podstępnego? Jak ktoś nie zna zasad koercji typów w JS, to nie powinien w tym języku pisać ;)
>Czy Ci się to podoba czy nie (jak to jest w moim przyp adku), klasy wprowadzone przez ES6, są tylko składniowym cukrem, którym możemy posypać prototypy.
Czyli mieli w wersji 6 przepisać fundamenty języka tylko dlatego, żeby wprowadzić klasy? IMO wgl ta składnia nie powinna zostać wprowadzona. Większość devów JS nie ogarnia prototypów, a jak się to przysłoni takim lukrem, to znajomość JS spadnie diametralnie. Wystarczy popatrzeć na ludzi, którzy znają jedynie CS…
>Jednak przyznaję, że extends wygląda przyjaźniej niż Child.prototype = new Parent()
Akurat to jest najgorszy przykład dziedziczenia w JS. Od dawna lepiej to machnąć przy pomocy Object.create. Z tym, że obydwa sposoby nam pokażą, że Child i tak nie jest potomkiem Parent.
W sumie jedyna sensowna przewaga extends (oprócz pokazywania poprawnych związków między „klasami”) to możliwość subklasowania natywnych „klas”. I to jest jedyny powód, dla którego sięgnąłbym po klasy.
>Możemy przyjąć w funkcji wiele parametrów jako tablica i pożegnać obiekt arguments.
tablicę*
>Mogą być wykożystane jako identyfikatory właściwości obiektów, w tym w WeakMap.
wykorzystane*
>Może się wydawać (mi tak się wydawało), że symbole są świetnym sposobem na symulowanie prywatnych właściwości (przez unikalne identyfikatory, niedostępne poza funkcją konstruktora). I wiecie co? Nie są.
One mają być po prostu unikalne. IMO Symbol to zbędny bajer i niepotrzebnie wprowadza aż tyle zmian do języka.
Co do iteratorów i generatorów: warto zwrócić uwagę na to, że generator de facto zwraca iterator.
Generatory jednak służą przede wszystkim do zarządzania asynchronicznym kodem i w połączeniu z Promises dają niesamowite możliwości, pozostawiając kod czysty i praktycznie „synchroniczny” (zresztą pisałem o tym na tym portalu z 2 lata temu).
>Jeżeli chcesz wiedzieć więcej o generatorach to sprawdź serię wpisów o generatorach w ES6autorstwa Davida Walsha.
One nie są autorstwa Davida Walsha, tylko Kyle’a Simpsona. Ja od siebie natomiast polecę blog 2ality.com – jego autor jest prawdziwym mistrzem jeśli chodzi o JS.
Co do literałów: śmieszne jest to zwłaszcza wówczas, gdy uświadomimy sobie, że literały takie zdeprecjonowano w ES5…
>Literały Szablonów
W życiu bym tak tego nie przetłumaczył. W ogóle to jest bolączka naszego community, że próbujemy tłumaczyć wszystko, nie podając równocześnie angielskich terminów. Jak ktoś przeczyta ten artykuł i zechce poszukać czegoś więcej na temat tego ficzera z ES6, to w życiu nie znajdzie tego, czego szuka. Template strings – tyle.
>Co więcej literał taki może otrzymać tag, który jest funkcją, a ta może go zmodyfikować.
Bez tego ten dodatek nie miałby najmniejszego sensu. Dzięki temu można było przepisać JSX na JS, pozbywając się całkowicie bzdurnej składni a’la E4X.
Przy Promises brakuje linku do specyfikacji: https://promisesaplus.com/
Co do modułów – TC-39 strzeliło sobie w stopę nie opierając składni modułów na tym zaproponowanym przez CJS. Co więcej – obecna implementacja loaderów IMO robi więcej szkody niż pożytku. Nie wspominając o nowym tagu module. Dlatego przez jeszcze bardzo długi czas standardem pozostanie UMD.
Bardzo mi brakuje opisu Reflection API.
Na wiele, rzeczy mogę odpowiedzieć krótko: „PRZEGLĄD ECMAScript 6”, na pisanie dokumentacji miałoby wątpliwy sens – już takie istnieją.
> Czyli mieli w wersji 6 przepisać fundamenty języka tylko dlatego, żeby wprowadzić klasy?
Im dłużej piszę w JS to patrząc na niego przez pryzmat np. Ruby to widzę, że przydałyby się mu solidne usprawnienia. Absurdy jak typeof NaN == „number” pomijam. Żeby to zrozumieć trzeba wiedzieć, że NaN jest liczbą przechowywaną w pamięci jako same jedynki – czy taka wiedza powinna być konieczna do zrozumienia sprawdzania typu? Moim zdaniem nie powinna być. Klasy mogłyby być dodane, (a nie zamiast) mimo, że prototypy mają więcej możliwości to widząc class w CoffeeScript cieszyłem się jak małe dziecko i gdybym był w tym odosobniony to CS nie zagościł by domyślnie w Railsach.
> Akurat to jest najgorszy przykład dziedziczenia w JS.
Jest wiele sposobów, nie ma „jednego poprawnego”. Jak mam obiekt i chce mu nadać 10 właściwości, które będą enumerowalne i własne to nie użyję Object.create, bo przy każdej muszę napisać jeszcze deskryptor.
> W życiu bym tak tego nie przetłumaczył.
Tłumacząc swój artykuł z angielskiego zdecydowałem, że przetłumaczę. Napisz własny i nie przetłumacz :P
> Co do iteratorów i generatorów: warto zwrócić uwagę na to, że generator de facto zwraca iterator.
Myślę, że dość jasno wynika to z pierwszego przykładu.
> >Możemy przyjąć w funkcji wiele parametrów jako tablica i pożegnać obiekt arguments.
> tablicę*
Widzę, że nawet taki JSowy maniak jak Ty się dał na to złapać. OBIEKT arguments jest „array like”, nie jest tablicą. Nie posiada np. metody pop by go nie modyfikować. Żeby uzyskać tablice potrzebujesz tego:
var args = Array.prototype.slice.call(arguments);
https://developer.mozilla.org/pl/docs/Web/JavaScript/Referencje/Funkcje/arguments
> Bardzo mi brakuje opisu Reflect API.
Tak jak pisałem, to jest przegląd. Nowe API obiektu niedługo będzie w repozytorium, na Reflect API też przyjdzie czas.
>Absurdy jak typeof NaN == „number” pomijam.
Akurat od tego jest funkcja isNaN, więc taka wiedza nie jest potrzebna. Poza tym raczej nie korzysta się z koercji typów w JS.
>gdybym był w tym odosobniony to CS nie zagościłby domyślnie w Railsach
No bo niestety nikt nie kapuje prototypów. To tutaj leży problem, nie w ich niedorobioności
>Jak mam obiekt i chce mu nadać 10 właściwości, które będą enumerowalne i własne to nie użyję Object.create, bo przy każdej muszę napisać jeszcze deskryptor
Ale przecież dodatkowe własności można nadać poza Object.create. to Object.defineProperty wymaga oznaczania enumerable w sposób dosłowny, nie Object.create (nie pierwszy parametr).
>Napisz własny i nie przetłumacz :P
Ok ;)
>Widzę, że nawet taki JSowy maniak jak Ty się dał na to złapać. OBIEKT arguments jest „array like”, nie jest tablicą.
Fajnie, ale ja poprawiałem „jako tablica” :D Poprawna forma to „jako tablicę”. No i ja tam wolę używać Array.from z polyfillem ;)
>Chętnie zaakceptuję jakieś pull requesty :D
A zobaczę. Na razie zajmuję się własnym artkiem o ES7 ;)
> Akurat od tego jest funkcja isNaN, więc taka wiedza nie jest potrzebna. Poza tym raczej nie korzysta się z koercji typów w JS.
Jasne, podobnie sprawa wygląda z Array.isArray. Chodzi o to, że jest to dziwne zachowanie – sytuacja nazbyt częsta w JS. Takich przykładów można mnożyć.
> No bo niestety nikt nie kapuje prototypów.
Douglas Crockford dobrze to tłumaczy – dużo osób go słucha i czyta. Myślę, że nie jest tak źle.
> Object.create (nie pierwszy parametr).
Object.create jest wygodny dla pewnych przypadków, a nie zawsze.
var obj = Object.create({foo: 1}, {bar: {value: 2}, baz: {value: 3, enumerable: true}});
var obj2 = new (function () { this.foo = 1; this.bar = 2, this.baz = 3 });
Object.keys(obj) // [„baz”]
Object.keys(obj.__proto__) // [„foo”]
Object.keys(obj2) // [„foo”, „bar”, „baz”] – pożądany wynik
Pierwszy parametr i tak nie jest ownProperty, pisałem „enumerowalne i własne”. Żeby z obj2 otrzymać taki sam wynik musiałbym jawnie napisać enumerable: true. Jestem daleki od tego, że jedna metoda jest lepsza od drugiej. Lepiej znać dwie i wybrać odpowiednią w danym momencie.
>Chodzi o to, że jest to dziwne zachowanie – sytuacja nazbyt częsta w JS.
Pogadajmy o PHP ;) każdy język jest dziwny. Po prostu na inny sposób.
>Myślę, że nie jest tak źle.
Masz szczęście w takim razie ;) ja widziałem już wystarczająco dużo.
>Pierwszy parametr i tak nie jest ownProperty, pisałem „enumerowalne i własne”
Ok, ale dziedziczy się prototypy. ownProperty to już broszka konstruktora, nie dziedziczenia.
Ale fakt – warto znać różne.
PHP wbrew pozorom nie jest złe. Gorzej sprawa wygląd już ze spójnością API :D
No popatrz – to jak z JS i DOM ;)
+ taka mała ciekawostka: http://ideone.com/wQA9A2
https://twitter.com/konklone/status/475819362172280833 :D
Circle extends Point to nie jest dobry przykład dziedziczenia. Koło nie jest punktem – koło jest zbiorem punktów opisanym przez współrzędne środka i promień. Zatem środek powinien być – podobnie jak promień – właściwością obiektu Circle.
„Klasy” w ES6 to cukier składniowy na prototypy, który pozwala tworzyć krótszy i bardziej czytelny kod – nic więcej. Jeśli programista nie zna narzędzia, którego używa, to jego problem. Nie widzę powodów do narzekania.
W kontekście płaszczyzny kartezjańskiej środek koła jest kluczowy. Tak to wygląda w szerszym kontekście: https://github.com/MichalZalecki/es6features-runtime/blob/master/src/es6/classes.js „Nie widzę powodów do narzekania.”
> „Klasy” w ES6 to cukier składniowy na prototypy
To właśnie napisałem, więc o co chodzi? Nawet jest w opisie testu! Chyba zdajesz sobie sprawę, że znajomość działania dziedziczenia poprzez prototypy nie równa się preferowaniu go nad dziedziczenie za pomocą klas.
http://zasoby.open.agh.edu.pl/~10sdczerner/page/zwiazki_pomiedzy_klasami – nie, koło nie jest punktem. Koło jest zbiorem punktów, a więc powiązanie, nie specjalizacja. To tak jakbyś stwierdził, że romb jest trójkątem, bo posiada wysokość.
Druga część była raczej skierowana w stronę socjalistów twierdzących, że skoro syntactic sugar przyczynia się do nieznajomości języka, to powinno go w tym języku nie być.
Tak samo kula nie jest kołem ale zaoszczędzimy sobie sporo pisania i powtarzania kodu tworząc kulę dziedziczącą po kole. Raczej nie warto tego roztrząsać.
Byłoby miło gdybyś nie uzupełniał argumentacji po odpowiedzi innego członka dyskusji.
Wybacz. Edytowałem gdy Ty pisałeś odpowiedź.
fajny artykuł.
mały błąd jaki znalazłem:
zamiast „destructing” powinno być „destructuring”
Dzięki za dużą porcję nowości z ES6. Patrząc czasami na składnię w powyższych przykładach zadawałem sobie pytanie, czy to nadal JS :)
Dzięki za ten post. Piszę teraz pierwszy projekt z użyciem ES6 i korzystam z twojego artykułu jako takiej „bazy wypadowej” póki nie przyswoję sobie wszystkich nowości i ich używanie nie będzie dla mnie naturalne. Dobra robota!