ECMAScript 6 / ES2015
Przydatne linki
- Krótkie wprowadzenie do ES6 po polsku
- Let's talk about ECMAScript 2015 po polsku
- Przegląd ECMAScript 6 z testami Karma/Jasmine po polsku
- Babel Learn ES2015
- ES6 Overview in 350 Bullet Points
- Książka You Don't Know JS: ES6 & Beyond z serii You Don't Know JS (book series)
- Książka Exploring ES6: Upgrade to the next version of JavaScript
- Design Patterns in ES6
Zmienne var
, let
i stała const
Zmienne var
W poniższym przykładzie zmienne a
, b
i c
mają zasięg globalny (zasięg wyższego bloku kodu). Zasięg dla var
tworzy się tylko w bloku funkcji lub catch
. Zasięg dla if
/ else
czy try
/ catch
/ finally
lub pętli nie tworzy się dla zmiennej var
, jest ona przypisywana do wyższego zasięgu, tj. do zasięgu wyższego bloku kodu.
var a = 1;
if (1 === a) {
var b = 2;
}
for (var c = 0; c < 3; c++) {
// …
}
function letsDeclareAnotherOne() {
var d = 4;
}
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(d); // ReferenceError: d is not defined
// window
console.log(window.a); // 1
console.log(window.b); // 2
console.log(window.c); // 3
console.log(window.d); // undefined
Źródło
Zmienne let
W przeciwieństwie do użycia var
, tylko zmienna a
ma zasięg globalny, a zmienne b
i c
mają zasięg blokowy. Zmienna let
przypisywana jest do zasięgu bloku kodu, np. do bloku if
/ else
czy try
/ catch
/ finally
lub pętli i nie jest dostępna z wyższego poziomu bloku kodu, tj. w poniższym przypadku nie mają one zasięgu globalnego. Ponadto let
nie ma hoistingu (const
i let
nie są wyciągane na początek kodu bloku przez kompilator).
let a = 1;
if (1 === a) {
let b = 2;
}
for (let c = 0; c < 3; c++) {
// …
}
function letsDeclareAnotherOne() {
let d = 4;
}
console.log(a); // 1
console.log(b); // ReferenceError: b is not defined
console.log(c); // ReferenceError: c is not defined
console.log(d); // ReferenceError: d is not defined
// window
console.log(window.a); // 1
console.log(window.b); // undefined
console.log(window.c); // undefined
console.log(window.d); // undefined
Źródło
let
w pętlach
Dla każdego obiegu pętli w przypadku użycia zmiennej let
tworzona jest kopia i
dla każdej iteracji, dzięki czemu nie otrzymujemy sześciu wartości 6 jak w przypadku użycia var
, tylko kolejno 1, 2, 3, 4 i 5.
Stałe const
Raz zainicjalizowana wartość stałej const
nie może zostać nadpisana. Ponadto nie jest dla nich przeprowadzany hoisting i muszą być zainicjalizowane przy definicji.
const RAND; // throws Uncaught SyntaxError: Missing initializer in const declaration
{
const PI = 3.141593;
PI = 3.14; // throws “PI” is read-only
}
console.log(PI); // throws ReferenceError: PI is not defined
const PI = 3.141593;
Źródło
Funkcje anonimowe - fat/arrow functions
// In ES5 we had only this syntax:
function() {
…
return …
}
// In ES6 arrow functions were introduced:
() => { … } // no argument
_ => { … } // https://stackoverflow.com/a/41085908
/* () says that the function does not expect any arguments, it doesn't declare any parameters. The function's .length is 0.
If you use _, it explicitly states that the function will be passed one argument, but that you don't care about it. The function's .length will be 1, which might matter in some frameworks. */
x => { … } // one argument
(x, y) => { … } // several arguments
x => { return x * x } // block
x => x * x // inline expression, same as above
Przykładowo zamiast:
[3, 4, 5].map(function (n) {
return n * n;
});
Można:
[3, 4, 5].map(n => n * n);
Źródło
fixed this
= lexical this
Arrow functions nie tworzą własnego obiektu this
, jak to ma miejsce w przypadku zwykłych funkcji anonimowych, gdzie this
nadrzędnej funkcji jest nadpisywane przez własną wartość this
.
Dla zobrazowania zamiast:
// ES5
function FancyObject() {
var self = this;
self.name = 'FancyObject';
setTimeout(function () {
self.name = 'Hello World!';
}, 1000);
}
Można:
// ES6
function FancyObject() {
this.name = 'FancyObject';
setTimeout(() => {
this.name = 'Hello World!'; // properly refers to FancyObject
}, 1000);
}
Źródło
Inne ciekawe informacje:
Argumenty funkcji - default, rest i spread
default
W ES5 nie można było tworzyć domyślnych wartości funkcji:
function inc(number, increment) {
// set default to 1 if increment not passed
// (or passed as undefined)
increment = increment || 1;
return number + increment;
}
console.log(inc(2, 2)); // 4
console.log(inc(2)); // 3
W ES6 natomiast:
function inc(number, increment = 1) {
return number + increment;
}
console.log(inc(2, 2)); // 4
console.log(inc(2)); // 3
Źródło
rest
W ES5 nie można było przekazywać dowolnej ilości argumentów w prosty sposób:
function sum() {
var numbers = Array.prototype.slice.call(arguments),
result = 0;
numbers.forEach(function (number) {
result += number;
});
return result;
}
console.log(sum(1)); // 1
console.log(sum(1, 2, 3, 4, 5)); // 15
W ES6 natomiast:
function sum(…numbers) {
var result = 0;
numbers.forEach(function (number) {
result += number;
});
return result;
}
console.log(sum(1)); // 1
console.log(sum(1, 2, 3, 4, 5)); // 15
Źródło
spread
W ES5 nie można było przekazywać tablicy w prosty sposób, rozbijając je na wejściowe argumenty funkcji bez użycia apply
:
function sum(a, b, c) {
return a + b + c;
}
var args = [1, 2, 3];
console.log(sum.apply(undefined, args)); // 6
W ES6 natomiast:
function sum(a, b, c) {
return a + b + c;
}
var args = [1, 2, 3];
console.log(sum(…args)); // 6
Lub:
function sum(a, b, c) {
return a + b + c;
}
var args = [1, 2];
console.log(sum(…args, 3)); // 6
Źródło
Dopasowanie wzorca (pattern matching) - destructuring
Zamiast:
// ES5
var point = [1, 2];
var xVal = point[0],
yVal = point[1];
console.log(xVal); // 1
console.log(yVal); // 2
Można:
// ES6
let point = [1, 2];
let [xVal, yVal] = point;
console.log(xVal); // 1
console.log(yVal); // 2
// .. and reverse!
[xVal, yVal] = [yVal, xVal];
console.log(xVal); // 2
console.log(yVal); // 1
Inne przykłady:
// ES6
// przykład z ominięciem elementu
let threeD = [1, 2, 3];
let [a, , c] = threeD;
console.log(a); // 1
console.log(c); // 3
// przykład z zagnieżdżaniem
let nested = [1, [2, 3], 4];
let [a, [b], d] = nested;
console.log(a); // 1
console.log(b); // 2
console.log(d); // 4
// przykład z literałem obiektu
let point = {
x: 1,
y: 2
};
let { x: a, y: b } = point;
console.log(a); // 1
console.log(b); // 2
// przykład z literałem obiektu i zagnieżdżaniem
let point = {
x: 1,
y: 2,
z: {
one: 3,
two: 4
}
};
let { x: a, y: b, z: { one: c, two: d } } = point;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(d); // 4
// przykład z literałem obiektu i tablicą
let mixed = {
one: 1,
two: 2,
values: [3, 4, 5]
};
let { one: a, two: b, values: ['c, , e'] } = mixed;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(e); // 5
// przykład z literałem obiektu i tablicą z funkcji
function mixed () {
return {
one: 1,
two: 2,
values: [3, 4, 5]
};
}
let { one: a, two: b, values: ['c, , e'] } = mixed();
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(e); // 5
Źródło
Łańcuchy znaków - strings
Szablony - template strings
Zamiast:
// ES5
var x = 1;
var y = 2;
var sumTpl = "" + x + " + " + y + " = " + (x + y);
console.log(sumTpl); // 1 + 2 = 3
Można:
// ES6
let x = 1;
let y = 2;
let sumTpl = `${x} + ${y} = ${x + y}`;
console.log(sumTpl); // 1 + 2 = 3
Źródło
Wiele linii
Zamiast:
// ES5
var types = "Number\nString\nArray\nObject";
console.log(types); // Number
// String
// Array
// Object
Można:
// ES6
let types = `Number
String
Array
Object`;
console.log(types); // Number
// String
// Array
// Object
Źródło
Znaki Unicode
O nowościach ES6 w ramach znaków Unicode można poczytać tutaj.
Pętla for-of
// ES6
const arr = [1, 2, 3, 4, 5];
for (let item of arr) {
console.log(item); // 1
// 2
// 3
// 4
// 5
}
Źródło
Funkcja generator
yield
- operator zwraca wartość z funkcji w momencie, gdy następuje zatrzymanie działania iteratora (wykonanie metody
next()
). function*
- specjalny rodzaj funkcji, która zwraca instancję generatora.
// ES6
function* generator () {
yield 1;
// pause
yield 2;
// pause
yield 3;
// pause
yield 'done?';
// done
}
let gen = generator(); // [object Generator]
console.log(gen.next()); // Object {value: 1, done: false}
console.log(gen.next()); // Object {value: 2, done: false}
console.log(gen.next()); // Object {value: 3, done: false}
console.log(gen.next()); // Object {value: 'done?', done: false}
console.log(gen.next()); // Object {value: undefined, done: true}
console.log(gen.next()); // Object {value: undefined, done: true}
for (let val of generator()) {
console.log(val); // 1
// 2
// 3
// 'done?'
}
Źródło
Klasy i dziedziczenie - classes and inheritance
Klasy
Zamiast:
// ES5
function Vehicle (name, type) {
this.name = name;
this.type = type;
};
Vehicle.prototype.getName = function getName () {
return this.name;
};
Vehicle.prototype.getType = function getType () {
return this.type;
};
var car = new Vehicle('Tesla', 'car');
console.log(car.getName()); // Tesla
console.log(car.getType()); // car
Można:
// ES6
class Vehicle {
constructor (name, type) {
this.name = name;
this.type = type;
}
getName () {
return this.name;
}
getType () {
return this.type;
}
}
let car = new Vehicle('Tesla', 'car');
console.log(car.getName()); // Tesla
console.log(car.getType()); // car
Źródło
Dziedziczenie
ECMAScript2015 wspiera dziedziczenie, wykonywanie kodu klas dziedziczonych (super), metody statyczne i instancyjne oraz konstrukcję obiektu poprzez słowo kluczowe constructor.
Aby rozbudować poprzednią klasę o dziedziczenie zamiast:
// ES5
function Car (name) {
Vehicle.call(this, name, ‘car’);
}
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;
Car.parent = Vehicle.prototype;
Car.prototype.getName = function () {
return 'It is a car: '+ this.name;
};
var car = new Car('Tesla');
console.log(car.getName()); // It is a car: Tesla
console.log(car.getType()); // car
Można:
// ES6
class Car extends Vehicle {
constructor (name) {
super(name, 'car');
}
getName () {
return 'It is a car: ' + super.getName();
}
}
let car = new Car('Tesla');
console.log(car.getName()); // It is a car: Tesla
console.log(car.getType()); // car
Źródło
Metody statyczne
// ES6
class Vehicle {
constructor (name, type) {
this.name = name;
this.type = type;
}
getName () {
return this.name;
}
getType () {
return this.type;
}
static create (name, type) {
return new Vehicle(name, type);
}
}
let car = Vehicle.create('Tesla', 'car');
console.log(car.getName()); // Tesla
console.log(car.getType()); // car
Źródło
Settery i gettery
// ES6
class Car {
constructor (name) {
this._name = name;
}
set name (name) {
this._name = name;
}
get name () {
return this._name;
}
}
let car = new Car('Tesla');
console.log(car.name); // Tesla
car.name = 'BMW';
console.log(car.name); // BMW
Źródło
Skrócone własności
Zamiast:
// ES5
var x = 1,
y = 2,
obj = {
x: x,
y: y
};
console.log(obj); // Object { x: 1, y: 2 }
Można:
// ES6
let x = 1,
y = 2,
obj = { x, y };
console.log(obj); // Object { x: 1, y: 2 }
Źródło
Wyliczane własności
Zamiast:
// ES5
var getKey = function () {
return '123';
},
obj = {
foo: 'bar'
};
obj['key_' + getKey()] = 123;
console.log(obj); // Object { foo: 'bar', key_123: 123 }
Można:
// ES6
let getKey = () => '123',
obj = {
foo: 'bar',
['key_' + getKey()]: 123
};
console.log(obj); // Object { foo: 'bar', key_123: 123 }
Źródło
Metody w obiekcie
Zamiast:
// ES5
var obj = {
name: 'object name',
toString: function () {
return this.name;
}
};
console.log(obj.toString()); // object name
Można:
// ES6
let obj = {
name: 'object name',
toString () { // 'function' keyword is omitted here
return this.name;
}
};
console.log(obj.toString()); // object name
Źródło
Moduły - modules
named export
// ES6
var multiply = function (x, y) {
return x * y;
};
export { multiply };
Lub:
export hello = 'Hello World';
export function multiply (x, y) {
return x * y;
};
// === OR ===
var hello = 'Hello World',
multiply = function (x, y) {
return x * y;
};
export { hello, multiply };
Źródło
import
Bazując na poprzednim przykładzie:
// ES6
import { hello } from './export';
// lub
import { hello, multiply } from './export';
// lub jako alias
import { multiply as pow2 } from './export';
// lub wyrażenie wildcard
import * as all from './export';
Źródło
default export
Domyślny eksport to najważniejsza wartość naszego modułu, występuje tylko raz:
// ES6
// export.js
export hello = 'Hello World';
export default function (x, y) {
return x * y;
};
// import.js
import pow2, { hello } from './export';
Źródło
Obietnice - promises
Przykład:
// ES6
let promise = new Promise((resolve, reject) => {
// when success, resolve
let value = 'success';
resolve(value);
// when an error occurred, reject
reject(new Error('Something happened!'));
});
promise.then(response => {
console.log(response);
}, error => {
console.log(error);
});
// success
Źródło
Obietnice a eventy
Promise za pomocą eventów:
img1.callThisIfLoadedOrWhenLoaded(function() {
// loaded
}).orIfFailedCallThis(function() {
// failed
});
// and…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
// all loaded
}).orIfSomeFailedCallThis(function() {
// one or more failed
});
Można teraz napisać:
img1.ready().then(function() {
// loaded
}, function() {
// failed
});
// and…
Promise.all([img1.ready(), img2.ready()]).then(function() {
// all loaded
}, function() {
// one or more failed
});
catch()
Przykład:
// ES6
let promise = new Promise(resolve => {
resolve();
});
promise
.then(response => {
return 1;
})
.then(response => {
throw new Error('failure');
})
.catch(error => {
console.log(error.message); // failure
});
Źródło
all()
Przykład:
// ES6
let doSmth = new Promise(resolve => {
resolve('doSmth');
}),
doSmthElse = new Promise(resolve => {
resolve('doSmthElse');
}),
oneMore = new Promise(resolve => {
resolve('oneMore');
});
Promise.all([
doSmth,
doSmthElse,
oneMore
])
.then(response => {
let [one, two, three] = response;
console.log(one, two, three); // doSmth doSmthElse oneMore
});
Źródło
Kolekcje Map()
i Set()
Map()
Mapy to para klucz-wartość. Są podobne do obiektów z wlaściwościami, ale jako klucz można użyć nie tylko string, ale też inne typy danych, jak na przykład funkcje.
Przykład użycia Map()
:
// ES6
let map = new Map(),
val2 = 'val2',
val3 = {
key: 'value'
};
map.set(0, 'val1');
map.set('1', val2);
map.set({ key: 2 }, val3);
// lub
// map = new Map([[0, 'val1'], ['1', val2], [{ key: 2 }, val3]]);
console.log(map);
// Map {0 => 'val1', '1' => 'val2', Object {key: 2} => Object {key: 'value'}}
console.log(map.get('1')); // val2
Źródło
Metody i właściwości
Przykład i prezentacja metod oraz właściwości Map()
: size
, set
, get
, has
, delete
, clear
, keys
, values
, entries,
forEach
i for-of
.
forEach
i for-of
// ES6
// forEach
map.forEach(function (value, key) {
console.log(`Key: ${key} has value: ${value}`);
// Key: 0 has value: val1
// Key: 1 has value: val2
// Key: [object Object] has value: [object Object]
});
// for-of
for (let entry of map) {
console.log(`Key: ${entry[0]} has value: ${entry[1]}`);
// Key: 0 has value: val1
// Key: 1 has value: val2
// Key: [object Object] has value: [object Object]
};
Źródło
Odpowiednik Array.prototype.map()
dla Map
Metoda forEach
nie zwraca żadnych wartości, wykonuje tylko funkcję na każdym elemencie obiektu Array
czy Map
. Należy wtedy zamienić obiekt Map
na tablicę - najłatwiej osiągnąć to za pomocą metody entries()
:
const myMap = new Map()
myMap.set('z98', 'foo')
myMap.set('123', 'bar')
myMap.set('abc', 'baz')
myMap.entries().map(function(result) {
const key = result[0]
const todo = result[1]
...
})
Pozostałe metody
set(key, value)
get(key)
- zwraca wartość przypisaną do klucza.entries()
- metoda ta zwraca całą kolekcję elementów w formie tablicy (a tak właściwie jako obiektMap iterator
, który zwraca elementy w formie tablicy).keys()
- zwraca jedynie klucze wszystkich elementów w formieMap iterator
.values()
- zwraca wyłącznie wszystkie wartości w formieMap iterator
.has(key)
- czy dany klucz znajduje się w naszej kolekcji, zwraca wartość typu boolean.delete(key)
- usuwa wybrany element.clear()
- usuwa wszystkie elementy.
Konwersja Object()
na Map()
Aby zmienić dowolny obiekt na obiekt typu Map()
możemy skorzystać z kilku metod, najbardziej zalecana (jednak najnowsza, ES2017) to Object.entries()
:
let obj = {foo: 'bar'}
const map = new Map(Object.entries(obj))
map.get('foo') // 'bar'
Natomiast nieco starsza metoda wykorzystuje Object.keys()
:
const map = new Map()
let obj = {foo: 'bar'}
Object.keys(obj).forEach(key => {
map.set(key, obj[key])
})
// or as a function
function buildMap(obj) {
let map = new Map()
Object.keys(obj).forEach(key => {
map.set(key, obj[key])
})
return map
}
const map = buildMap({foo: 'bar'});
Dla dociekliwych, implementacja metody entries()
wygląda mniej więcej tak:
function* entries(obj) {
for (let key in obj)
yield [key, obj[key]]
}
const map = new Map(entries({foo: 'bar'}))
map.get('foo') // 'bar'
Set()
W odróżnieniu od Map()
zawiera pojedyńcze wartości, które muszą być unikalne:
// ES6
let set = new Set()
set.add(1)
set.add('1')
set.add({ key: 'value' })
// lub
// let set = new Set([1, '1', { key: 'value' }])
console.log(set) // Set {1, '1', Object {key: 'value'}}
Udostępnia podobny zbiór metod, co Map()
. Zamiast metody set(key, value)
, mamy do dyspozycji metodę add(value)
. Przykład i prezentacja metod API Set()
.Źródło
WeakMap()
Kolekcje typu Weak
pozwalają zwalniać zasoby w momencie, gdy elementy przestają istnieć. W ten sposób garbage collector jest w stanie usunąć wystąpienia danych obiektów z kolekcji i zwolnić pamięć. Kluczami kolekcji mogą być wyłącznie obiekty.
// ES6
let wm = new WeakMap(),
obj = {
key1: {
k: 'v1'
},
key2: {
k: 'v2'
}
};
wm.set(obj.key1, 'val1');
wm.set(obj.key2, 'val2');
console.log(wm); // WeakMap {Object {k: 'v1'} => 'val1', Object {k: 'v2'} => 'val2'}
console.log(wm.has(obj.key1)); // true
delete obj.key1;
console.log(wm.has(obj.key1)); // false
Udostępnia podobny zbiór metod, co Map()
. Nie możemy jednak po nich iterować za pomocą metody forEach
oraz pętli for-of
. Ponadto pole size
nie istnieje i możemy użyć tylko set
, get
, has
i delete
.Źródło
WeakSet()
Kolekcje typu Weak
pozwalają zwalniać zasoby w momencie, gdy elementy przestają istnieć. W ten sposób garbage collector jest w stanie usunąć wystąpienia danych obiektów z kolekcji i zwolnić pamięć. Kluczami kolekcji mogą być wyłącznie obiekty.
// ES6
let ws = new WeakSet(),
obj = {
key1: {
k: 'v1'
},
key2: {
k: 'v2'
}
};
ws.add(obj.key1);
ws.add(obj.key2);
console.log(ws); // WeakSet {Object {k: 'v1'}, Object {k: 'v2'}}
console.log(ws.has(obj.key1)); // true
delete obj.key1;
console.log(ws.has(obj.key1)); // false
Udostępnia podobny zbiór metod, co Set()
. Nie możemy jednak po nich iterować za pomocą metody forEach
oraz pętli for-of
. Ponadto pole size
nie istnieje i możemy użyć tylko add
, get
, has
i delete
.Źródło