Repozytorium Web Developera

ECMAScript 6 / ES2015

Przydatne linki

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.

Źródło

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

    () => { … }            // no argument
     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. 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

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

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

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.

// ES6
const arr = [1, 2, 3, 4, 5];
for (let item of arr) {
  console.log(item); // 1
                     // 2
                     // 3
                     // 4
                     // 5
}
Źródło

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

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

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

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

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

forEach i for-of

Przykład i prezentacja metod API Map(): set, size, get, has, delete, clear, keys, values, entries, 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

Pozostałe metody

  • set(key, value)
  • get(key)
  • entries() - w zasadzie wynik wywołania nie różni się niczym od powyższych przykładów. Metoda entries() zwraca całą kolekcję elementów.
  • keys() - zwraca jedynie klucze wszystkich elementów.
  • values() - zwraca wyłącznie wszystkie wartości.
  • has(key) - czy dany klucz znajduje się w naszej kolekcji, zwraca wartość typu boolean
  • delete(key) - usuwa wybrany element
  • clear() - usuwa wszystkie elementy
Źródło

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