Repozytorium Web Developera

Archiwum z lat 2013-2018, treści mogą być nieaktualne.

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


// 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:

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

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]
  ...
})

Źródło

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 obiekt Map iterator, który zwraca elementy w formie tablicy).
  • keys() - zwraca jedynie klucze wszystkich elementów w formie Map iterator.
  • values() - zwraca wyłącznie wszystkie wartości w formie Map iterator.
  • 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

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'

Ź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

Check ES7/2016 features