ES6

ES6

let, const и блочная область видимости

Ключевое слово let позволяет объявлять переменные с ограниченной областью видимости — только для блока {…}, в котором происходит объявление. Это называется блочной областью видимости. Вместо ключевого слова var, которое обеспечивает область видимости внутри функции, стандарт ES6 рекомендует использовать let.

var a = 2;
{
    let a = 3;
    console.log(a); // 3
}
console.log(a); // 2

Другой формой объявления переменной с блочной областью видимости является ключевое слово const. Оно предназначено для объявления переменных (констант), значения которых доступны только для чтения. Это означает не то, что значение константы неизменно, а то, что идентификатор переменной не может быть переприсвоен.
Вот простой пример:

{
    const ARR = [5, 6];
    ARR.push(7);
    console.log(ARR); // [5,6,7]
    ARR = 10; // TypeError
    ARR[0] = 3; // значение можно менять
    console.log(ARR); // [3,6,7]
}

О чём стоит помнить:

  • Когда дело касается поднятия переменных (hoisting) let и const, их поведение отличается от традиционного поведения var и function. И let и const не существуют до своего объявления (от переводчика: для подробностей автор оригинального руководства отсылает к статье Temporal Dead Zone)
  • Областью видимости let и const является ближайший блок.
  • При использовании const рекомендуется использовать ПРОПИСНЫЕ_БУКВЫ.
  • В const одновременно с объявлением переменной должно быть присвоено значение.

Стрелочные функции

Стрелочные функции представляют собой сокращённую запись функций в ES6. Стрелочная функция состоит из списка параметров ( ... ), за которым следует знак => и тело функции.

// Классическое функциональное выражениеlet addition = function(a, b) {
    return a + b;
};

// Стрелочная функцияlet addition = (a, b) => a + b;

Заметим, что в примере выше, тело функции представляет собой краткую запись, в которой не требуется явного указания на то, что мы хотим вернуть результат.
А вот пример с использованием блока из фигурных скобок:

let arr = ['apple', 'banana', 'orange'];

let breakfast = arr.map(fruit => {
    return fruit + 's';
});

console.log(breakfast); // ['apples', 'bananas', 'oranges']

Это ещё не всё!…
Стрелочные функции не просто делают код короче. Они тесно связаны с ключевым словом this и привязкой контекста.
Поведение стрелочных функций с ключевым словом this отличается от поведения обычных функций сthis. Каждая функция в JavaScript определяет свой собственный контекст this, но внутри стрелочных функций значение this то же самое, что и снаружи (стрелочные функции не имеют своего this). Посмотрим на следующий код:

function Person() {
    // Конструктор Person() определяет `this` как экземпляр самого себя.
    this.age = 0;

    setInterval(function growUp() {
        // Без использования `use strict`, функция growUp() определяет `this`
        // как глобальный объект, который отличается от `this`,
        // определённого конструктором Person().
        this.age++;
    }, 1000);
}
var p = new Person();

В ECMAScript 3/5 это поведение стало возможным изменить, присвоив значение this другой переменной.

function Person() {
    var self = this;
    self.age = 0;

    setInterval(function growUp() {
        // Коллбэк относится к переменной `self`,
        // значением которой является ожидаемый объект.
        self.age++;
    }, 1000);
}

Как сказано выше, внутри стрелочных функций значение this то же самое, что и снаружи, поэтому следующий код работает так, как от него и ожидается:

function Person() {
    this.age = 0;

    setInterval(() => {
        this.age++; // `this` относится к объекту person
    }, 1000);
}

var p = new Person();

Параметры по умолчанию

ES6 позволяет установить параметры по умолчанию при объявлении функции. Вот простой пример:

let getFinalPrice = (price, tax = 0.7) => price + price * tax;
getFinalPrice(500); // 850, так как значение tax не задано

getFinalPrice(500, 0.2); // 600, значение tax по-умолчанию заменяется на 0.2

Spread / Rest оператор

... оператор называют как spread или rest, в зависимости от того, как и где он используется.
При использовании в любом итерируемом объекте (iterable), данный оператор «разбивает» («spread») его на индивидуальные элементы:

function foo(x, y, z) {
    console.log(x, y, z);
}

let arr = [1, 2, 3];
foo(...arr); // 1 2 3

Другим распространённым использованием оператора ... является объединение набора значений в один массив. В данном случае оператор работает как «rest» (от переводчика: не нашёл подходящего перевода на русский язык, из примера ниже всё станет ясно)

function foo(...args) {
    console.log(args);
}
foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

Расширение возможностей литералов объекта

ES6 позволяет объявить литералы объекта с помощью короткого синтаксиса для инициализации свойств из переменных и определения функциональных методов. Также, стандарт обеспечивает возможность вычисления свойств непосредственно в литерале объекта.

function getCar(make, model, value) {
    return {
        // с синтаксисом короткой записи можно
        // пропускать значение свойства, если оно
        // совпадает с именем переменной, значение
        // которой мы хотим использовать
        make,  // аналогично make: make
        model, // аналогично model: model
        value, // аналогично value: value

        // вычисляемые свойства теперь работают в
        // литералах объекта
        ['make' + make]: true,

        // Короткая запись метода объекта пропускает
        // ключевое слово `function` и двоеточие. Вместо
        // "depreciate: function() {}" можно написать:
        depreciate() {
            this.value -= 2500;
        }
    };
}

let car = getCar('Kia', 'Sorento', 40000);
console.log(car);
// {//     make: 'Kia',//     model:'Sorento',//     value: 40000,//     makeKia: true,//     depreciate: function()// }

Восьмеричный и двоичный литералы

В ES6 появилась новая поддержка для восьмеричных и двоичных литералов.
Добавление к началу числа 0o или 0O преобразует его в восьмеричную систему счисления (аналогично,0b или 0B преобразует в двоичную систему счисления). Посмотрим на следующий код:

let oValue = 0o10;
console.log(oValue); // 8

let bValue = 0b10;
console.log(bValue); // 2

Деструктуризация массивов и объектов

Деструктуризация помогает избежать использования вспомогательных переменных при взаимодействии с объектами и массивами.

function foo() {
    return [1, 2, 3];
}
let arr = foo(); // [1,2,3]

let [a, b, c] = foo();
console.log(a, b, c); // 1 2 3

function bar() {
    return {
        x: 4,
        y: 5,
        z: 6
    };
}
let { x: a, y: b, z: c } = bar();
console.log(a, b, c); // 4 5 6

Ключевое слово super для объектов

ES6 позволяет использовать метод super в (безклассовых) объектах с прототипами. Вот простой пример:

var parent = {
    foo() {
        console.log("Привет от Родителя!");
    }
}

var child = {
    foo() {
        super.foo();
        console.log("Привет от Ребёнка!");
    }
}

Object.setPrototypeOf(child, parent);
child.foo(); // Привет от Родителя!
             // Привет от Ребёнка!

Строковые шаблоны и разделители

ES6 предоставяляет более простой способ вставки значения переменной или результата выражения (т.н. «интерполяцию»), которые рассчитываются автоматически.

  • ${ ... } используется для вычисления значения переменной/выражения.
  • « Обратные кавычки используются как разделитель для таких случаев.
let user = 'Кевин';
console.log(`Привет, ${user}!`); // Привет, Кевин!

for…of против for…in

  • for...of используется для перебора в цикле итерируемых объектов, например, массивов.
let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname of nicknames) {
    console.log(nickname);
}
// di// boo// punkeye
  • for...in используется для перебора в цикле всех доступных для перебора (enumerable) свойств объекта.
let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname in nicknames) {
    console.log(nickname);
}
// 0// 1// 2// size

Map и WeakMap

ES6 представляет новые структуры данных — Map и WeakMap. На самом деле, мы используем «Map» в JavaScript всё время. Каждый объект можно представить как частный случай Map.
Классический объект состоит из ключей (всегда в строковом виде) и значений, тогда как в Map для ключа и значения можно использовать любое значение (и объекты, и примитивы). Посмотрим на этот код:

var myMap = new Map();

var keyString = "строка",
    keyObj = {},
    keyFunc = function() {};

// устанавливаем значения
myMap.set(keyString, "значение, связанное со 'строка'");
myMap.set(keyObj, "значение, связанное с keyObj");
myMap.set(keyFunc, "значение, связанное с keyFunc");

myMap.size; // 3

// получаем значения
myMap.get(keyString);    // "значение, связанное со 'строка'"
myMap.get(keyObj);       // "значение, связанное с keyObj"
myMap.get(keyFunc);      // "значение, связанное с keyFunc"

WeakMap
WeakMap это Map, в котором ключи обладают неустойчивыми связями, что позволяет не мешать сборщику мусора удалять элементы WeakMap. Это означает, что можно не беспокоиться об утечках памяти.
Стоить отметить, что в WeakMap, в отличие от Map, каждый ключ должен быть объектом.
Для WeakMap есть только четыре метода: delete(ключ), has(ключ), get(ключ) и set(ключ, значение).

let w = new WeakMap();
w.set('a', 'b');
// Uncaught TypeError: Invalid value used as weak map key

var o1 = {},
    o2 = function(){},
    o3 = window;

w.set(o1, 37);
w.set(o2, "azerty");
w.set(o3, undefined);

w.get(o3); // undefined, потому что это заданное значение

w.has(o1); // true
w.delete(o1);
w.has(o1); // false

Set и WeakSet

Объекты Set это коллекции уникальных значений. Дублированные значения игнорируются, т.к. коллекция должна содержать только уникальные значения. Значения могут быть примитивами или ссылками на объекты.

let mySet = new Set([1, 1, 2, 2, 3, 3]);
mySet.size; // 3
mySet.has(1); // true
mySet.add('строки');
mySet.add({ a: 1, b:2 });

Вы можете перебирать Set в цикле с помощью forEach или for...of. Перебор происходит в том же порядке, что и вставка.

mySet.forEach((item) => {
    console.log(item);
    // 1
    // 2
    // 3
    // 'строки'
    // Object { a: 1, b: 2 }
});

for (let value of mySet) {
    console.log(value);
    // 1
    // 2
    // 3
    // 'строки'
    // Object { a: 1, b: 2 }
}

У Set также есть методы delete() и clear().
WeakSet
Аналогично WeakMap, объект WeakSet позволяет хранить объекты с неустойчивыми связями в коллекции. Объект в WeakSet уникален.

var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false, foo не был добавлен к коллекции

ws.delete(window); // удаляет window из коллекции
ws.has(window);    // false, window был удалён

Классы в ES6

В ES6 представили новый синтаксис для классов. Здесь стоит отметить, что класс ES6 не представляет собой новую объектно-ориентированную модель наследования. Это просто синтаксический сахар для существующего в JavaScript прототипного наследования.
Класс в ES6 представляет собой просто новый синтаксис для работы с прототипами и функциями-конструкторами, которые мы привыкли использовать в ES5.
Функции, записанные с помощью ключевого слова static, используются для объявления статических свойств класса.

class Task {
    constructor() {
        console.log("Создан экземпляр task!");
    }

    showId() {
        console.log(23);
    }

    static loadAll() {
        console.log("Загружаем все tasks...");
    }
}

console.log(typeof Task); // functionlet task = new Task(); // "Создан экземпляр task!"
task.showId(); // 23
Task.loadAll(); // "Загружаем все tasks..."

extends и super в классах
Посмотрим на следующий код:

class Car {
    constructor() {
        console.log("Создаём новый автомобиль");
    }
}

class Porsche extends Car {
    constructor() {
        super();
        console.log("Создаём Porsche");
    }
}

let c = new Porsche();
// Создаём новый автомобиль// Создаём Porsche

В ES6 ключевое слово extends позволяет классу-потомку наследовать от родительского класса. Важно отметить, что конструктор класса-потомка должен вызывать super().
Также, в классе-потомке можно вызвать метод родительского класса с помощьюsuper.имяМетодаРодителя().

О чём стоит помнить:

  • Объявления классов не поднимаются наверх (not hoisted). Сначала нужно объявить класс и только после этого использовать его, иначе будет ошибка ReferenceError.
  • Нет необходимости использовать ключевое слово function во время задания функций внутри определения класса.

Тип данных Symbol

Symbol это уникальный и неизменяемый тип данных, представленный в ES6. Целью Symbol является создание уникального идентификатора, к которому нельзя получить доступ.
Вот как можно создать Symbol:

var sym = Symbol("опциональное описание");
console.log(typeof sym); // symbol

Заметим, что использовать new вместе с Symbol(…) нельзя.
Если Symbol используется как свойство/ключ объекта, он сохраняется таким специальным образом, что свойство не будет показано при нормальном перечислении свойств объекта.

var o = {
    val: 10,
    [Symbol("случайный")]: "Я - символ",
};

console.log(Object.getOwnPropertyNames(o)); // val

Чтобы извлечь символьные свойства объекта, нужно использовать Object.getOwnPropertySymbols(o)

Итераторы

Итератор обращается к элементам коллекции по одному, в то же время сохраняя память о своей текущей позиции в этой коллекции. У итератора есть метод next(), который возвращает следующий элемент в последовательности. Этот метод возвращает объект с двумя свойствами: done (окончен ли перебор) и value (значение).
В ES6 есть метод Symbol.iterator, который определяет итератор для объекта по-умолчанию. При каждой необходимости перебора в цикле для объекта (например, в начале цикла for..of), его метод итератора вызывается без аргументов, и возвращённый итератор используется для того, чтобы получить значения для перебора.
Посмотрим на массив, который является перебираемым (iterable), и на итератор, который есть у массива для обработки его значений:

var arr = [11,12,13];
var itr = arr[Symbol.iterator]();

itr.next(); // { value: 11, done: false }
itr.next(); // { value: 12, done: false }
itr.next(); // { value: 13, done: false }

itr.next(); // { value: undefined, done: true }

Заметим, что можно написать собственный итератор через определение obj[Symbol.iterator]() с описанием объекта.

Генераторы

Функции-генераторы представляют собой новую особенность ES6, которая позволяет функции создавать много значений в течение некоторого периода времени, возвращая объект (называемый генератором), который может быть итерирован для выброса значений из функции по одному за раз.
Функция-генератор возвращает итерируемый объект при своём вызове.
Функция-генератор записывается с помощью знака * после ключевого слова function, а в теле функции должно присутствовать ключевое слово yield.

function *infiniteNumbers() {
    var n = 1;
    while (true) {
        yield n++;
    }
}

var numbers = infiniteNumbers(); // возвращает перебираемый объект

numbers.next(); // { value: 1, done: false }
numbers.next(); // { value: 2, done: false }
numbers.next(); // { value: 3, done: false }

Каждый раз при вызове yield возвращённое значение становится следующим значением в последовательности.
Также заметим, что генераторы вычисляют свои возвращённые значения по запросу, что позволяет им эффективно представлять последовательности, затратные с точки зрения вычислений, или даже бесконечные последовательности.

Промисы

В ES6 появилась встроенная поддержка промисов. Промис это объект, который ждёт выполнения асинхронной операции, после которого (т.е. после выполнения) промис принимает одно из двух состояний: fulfilled (resolved, успешное выполнение) или rejected (выполнено с ошибкой).
Стандартным способом создания промиса является конструктор new Promise(), который принимает обработчик с двумя функциями как параметрами. Первый обработчик (обычно именуемый resolve) представляет собой функцию для вызова вместе с будущим значением, когда оно будет готово; второй обработчик (обычно именуемый reject) является функцией, которая вызывается для отказа от выполнения промиса, если он не может определить будущее значение.

var p = new Promise(function(resolve, reject) {  
    if (/* условие */) {
        resolve(/* значение */);  // fulfilled successfully (успешный результат)
    } else {
        reject(/* reason */);  // rejected (ошибка)
    }
});

Каждый промис обладает методом then, в котором есть два коллбэка. Первый коллбэк вызывается, если промис успешно выполнен (resolved), тогда как второй коллбэк вызывается, если промис выполнен с ошибкой (rejected).

p.then((val) => console.log("Промис успешно выполнен", val),
       (err) => console.log("Промис выполнен с ошибкой", err));

При возвращении значения от then коллбэки передадут значение следующему коллбэку then.

var hello = new Promise(function(resolve, reject) {  
    resolve("Привет");
});

hello.then((str) => `${str} Мир`)
     .then((str) => `${str}!`)
     .then((str) => console.log(str)) // Привет Мир!

При возвращении промиса, успешно обработанное значение промиса пройдёт к следующему коллбэку, для того, чтобы эффективно соединить их вместе.
Эта простая техника помогает избежать ада с коллбэками («callback hell»).

var p = new Promise(function(resolve, reject) {  
    resolve(1);
});

var eventuallyAdd1 = (val) => {
    return new Promise(function(resolve, reject){
        resolve(val + 1);
    });
}

p.then(eventuallyAdd1)
 .then(eventuallyAdd1)
 .then((val) => console.log(val)) // 3