Современный JavaScript. Основы ECMAScript 6

ECMAScript 6 — версия языка JavaScript, стандарт которой утвердился в 2015 году.

Для использования возможностей ES6 необходим либо запуск скриптов в окружении, поддерживающем эту версию языка, либо использования специальных программ — транспайлеров, например, Babel (пример работы Babel — тут). Поддержку возможностей ES6 для различных окружений можно посмотреть здесь.

Переменные let и const

Стандарт добавляет два нового вида переменных: let и const. Исользуются они так же, как и переменные var:

let myMoney = 0.05;
const humanStupidity = Infinity;

Но данные виды переменных имеют следующие особенности использования:

Обычно для констант, содержащих примитивы, имя задается прописными буквами, а для констант-объектов — строчными.

При написании кода обычно руководствуются подходом: объявлять максимум переменных как const, а те, которые будут меняться, — как let.

Деструктуризация

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

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

При использовании квадратных скобок можно присвоить элементы массива переменным, находящимся на соответсвующих местах:

let suitcase = ["cap", "t-shirt", "tie"];
let [item1, item2, item3] = suitcase;

console.log(item1); // cap
console.log(item2); // t-shirt
console.log(item3); // tie

При необходимости можно пропустить какие-либо перменные, тем самым отбросив ненужные элементы массива:

let suitcase = ["cap", "t-shirt", "tie"];
let [, myFavouriteItem] = suitcase;

console.log(myFavouriteItem); // t-shirt

Spread или rest

Для того, чтобы получить элементы массива, если мы заранее не знаем их количества, мы можем использовать оператор spread (троеточие), который поместит оставшиеся элементы в указанную переменную:

let suitcase = ["cap", "t-shirt", "tie", "socks"];
let [item1, ...restItems] = suitcase;

console.log(item1); // cap
console.log(restItems); // ["t-shirt", "tie", "socks"]

Значения по умолчанию

Если элементов в массиве меньше, чем запрашиваемых переменных, то "лишние" переменные получат undefined:

let suitcase = ["cap", "t-shirt"];
let [,,item3] = suitcase;

console.log(item3); // undefined

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

let suitcase = ["cap", "t-shirt"];
let [,,item3="pants"] = suitcase;

console.log(item3); // pants

В качестве значения по умолчанию может быть не только примитив, но и объект или выражение:

let suitcase = ["cap", "t-shirt"];
let [,,item3=3+1, item4=Math.round(1.49)] = suitcase;

console.log(item3); // 4
console.log(item4); // 1

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

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

let suitcase = {
    item1: "cap",
    item2: "t-shirt"
};
let {item1, item2} = suitcase;

console.log(item1); // cap
console.log(item2); // t-shirt

Если необходимо получить переменную с именем, отличным от названия свойства, то новое имя указывают через двоеточие внутри деструктуризирующих скобок:

let suitcase = {
    item1: "cap",
    item2: "t-shirt"
};
let {item1: vintageItem, item2: modernItem} = suitcase;

console.log(vintageItem); // cap
console.log(modernItem); // t-shirt

Как и в случае деструктуризации массива можно указать параметры по умолчанию:

let suitcase = {
    item1: "cap",
    item2: "t-shirt"
};
let {item1: blackItem, item3: whiteItem="pants", greyItem="boa"} = suitcase;

console.log(blackItem); // cap
console.log(whiteItem); // pants
console.log(greyItem); // boa

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

Вложенные деструктуризации

Даже если объект для "разбора" достаточно сложен, его можно деструктуризировать сразу:

let suitcase = {
    item1: "cap",
    item2: "t-shirt",
    socks: {
        count: 2,
        color: "#000"
    },
    box: ["toothbrush", "toothpaste"]
};
let {item1, socks:{count: socksCount=5}, box:[,boxedItem]} = suitcase;

console.log(item1); // cap
console.log(socksCount); // 2
console.log(boxedItem); // toothpaste

Функции

Как и в строгом режиме, в ES6 нет однозначной привязки между аргументами функции и arguments.

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

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

function plusTwo(num=0) {
    return num + 2;
}

console.log(plusTwo(2)); // 4
console.log(plusTwo()); // 2

Параметр по умолчанию используется для отсутствующего или равного undefined аргумента.

Параметры по умолчанию могут быть значениями:

function plusTwo(num=1+Math.ceil(2*Math.random())) {
    return num + 2;
}

console.log(plusTwo()); // 4
console.log(plusTwo()); // 5

Функция параметра по умолчанию вызовется только в том случае, если этот параметр понадобится.

Spread для функций

Подобно деструктуризации, оператор spread может собирать аргументы функции:

function sum(num, ...rest) {
    console.log(num); // 1
    console.log(rest); // [2, 3, 4]
    return 42;
}

sum(1, 2, 3, 4);

При использовании оператора троеточие не в конце списка аргументов возникнет ошибка, т.к. он собирает "оставшиеся" параметры, и после него уже никаких параметров быть не может.

Оператор ... может использоваться как для преобразования списка аргументов в массив, так и наоборот. Например, для передачи массива не как одного аргумента, а как набора аргументов:

let nums = [1,2,3,4];
console.log( Math.max(...nums) );

Деструктуризация в параметрах

При передаче аргумента-объекта его также можно деструктуризовать "на месте":

let suitcase = {
    item1: "cap",
    item2: "t-shirt",
    socks: {
        count: 2,
        color: "#000"
    },
    box: ["toothbrush", "toothpaste"]
};
function whatIsInSuitcase({item1, socks:{count}}) {
    console.log("I have " + item1 + " and " + count + " socks in my suitcase."); // I have cap and 2 socks in my suitcase.
}

whatIsInSuitcase(suitcase);

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

function whatIsInSuitcase({item1="nothing", socks:{count=1}}) {
    console.log("I have " + item1 + " and " + count + " socks in my suitcase."); // I have nothing and 1 socks in my suitcase.
}

whatIsInSuitcase({socks:{}});

Чтобы запускать функцию без параметра нужно указать ожидаемую структуру объекта в параметре по умолчанию:

function whatIsInSuitcase({item1="nothing", socks:{count=42}} = {socks:{}}) {
    console.log("I have " + item1 + " and " + count + " socks in my suitcase."); // I have nothing and 42 socks in my suitcase.
}

whatIsInSuitcase();

Функция в блоке

Подобно let и const функция, объявленная внутри блока, не видна за его пределами, даже если она объявлена как Function Declaration. Чтобы добиться интерпретации такого кода как кода ES6, нужно указать "use strict".

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

В ES6 функциям добавили "стрелочный" синтаксис:

let plusTwo = (num) => num + 2;
console.log( plusTwo(2) ); // 4

Для функции без аргументов используются пустые скобки:

let return42 = () => 42;
console.log( return42() ); // 42

Если нужно поместить в тело функции несколько строк кода, то используют фигурные скобки:

let return42 = () => {
    let start = 10;
    start *= 4;
    start += 2;
    return start;
};
console.log( return42() ); // 42

Особенностью стрелочных функций также является отсутсвие собственного this, что делает удобных их использование в функциях callbackах:

let suitcase = {
    type: "vintage",
    items: ["pants", "cap", "t-shirt"],
    showList: function() {
        this.items.forEach((v) => console.log(this.type + " : " + v) );
    }
};
suitcase.showList();

Из-за отсутствия this стрелочные функции нельзя использовать в качестве конструкторов.

Стрелочные функции не имеют arguments.

Строки

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

Был добавлен синтаксис строковых шаблонов:

let str = `my string`;

Особенности:

Теги шаблонизации

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

let str = lol`${2} + ${2} is ${2 + 2} \n sure`;
function lol (literals, ...values) {
    console.log(literals); // ["", " + ", " is ", " ↵ sure"
    console.log(values); // [2, 2, 4]
}

Массив literals так же содержит свойство raw, в котром литералы содержатся в исходном виде.

Тег шаблонизации может изменить шаблон и вернувшийся результат присвоить строке.

Новые методы

Для строк добавлены новые методы:

Использование фигурных скобок в записях с '\u' позволяет указать в качестве кода символа более, чем четырехзначное число.

Объекты и прототипы

Короткие свойства

При создании объектов можно воспользоваться синтаксисом, внешне похожим на "деструктуризацию наоборот":

let item1 = "pants";
let item2 = "cap";
let suitcase = {
    item1,
    item2
};
console.log(suitcase);

Заключив имя свойства в квадратные скобки, можно использовать выражения для вычиления их названий:

let propName = "item1";
let suitcase = {
    [propName]: "t-shirt"
};
console.log(suitcase);

Геттер-сеттер для прототипа

К методу Object.getPrototypeOf(obj) добавили сеттер прототипа Object.setPrototypeOf(obj, newProto). Так же разрешен прямой доступ к объекту __proto__.

Object.assign

Метод assign копирует в свой первый аргумент-объект свойства из всех остальных аргументов-объектов. Все последующие свойства перезаписывают предыдущие.

Object.is

Метод is сравнивает значения двух своих аргументов, почти как это делает ===, но имеет особенности: с помощью такого сравнения NaN равен NaN.

Методы объекта

Был добавлен более короткий синтаксис создания методов объекта:

let suitcase = {
    item1: "tie",
    open() {
        console.log("Suitcase is opened");
    }
};

В таком же стиле можно добавлять гетеры и сеттеры свойств:

let suitcase = {
    myState: "opened",
    get state() {
        return `Suitcase is ${this.myState}`;
    }
};
console.log(suitcase.state);

Таким же образом можно добавить метод с вычисляемым названием:

let myMethod = "close";
let suitcase = {
    item1: "tie",
    [myMethod]() {
        console.log("Suitcase is closed");
    }
};

super

В ES6 с помощью ключевого слова super в методе объекта можно получить свойство его прототипа:

let item = {
    volume: 10
};
let suitcase = {
    __proto__: item,
    volume: 20,
    info() {
        console.log(`My new volume is ${this.volume},
        my ex one is ${super.volume}`
);
    }
};
suitcase.info();