Вероятно, вы уже слышали об ECMAScript 6, или ES6.
Это новая версия языка JavaScript, в которой было реализовано множество полезных особенностей.
Переменные
- let
Для объявления переменной мы привыкли использовать ключевое слово var. Теперь так же можно использовать ключевое слово let. Они отличаются областью видимости объявляемой переменной. Переменная, объявленная с помощью var видна в пределах всей функции, переменная, объявленная с помощью let - только в пределах блока.
if (true) { let x = 1; } console.log(x); // undefined
if (true) { var x = 1; } console.log(x); // 1
Это делает код чище, если переменная используется только в блоке - она доступна только в этом блоке. Особенно это актуально для цикла for:
for (let i = 0, length = list.length; i < length; i++) { // do something with list[i] } console.log(i); // undefined
У объявлений переменной через let есть три основных отличия от var:
var apples = 5; if (true) { var apples = 10; alert(apples); // 10 (внутри блока) } alert(apples); // 10 (снаружи блока то же самое)
В примере выше apples - одна переменная на весь код, которая модифицируется в if.
То же самое с let будет работать по-другому:
let apples = 5; if (true) { let apples = 10; alert(apples); // 10 (внутри блока) } alert(apples); // 5 (снаружи блока значение не изменилось)
Здесь, фактически, две независимые переменные apples, одна - глобальная, вторая - в блоке if. Заметим, что если объявление let apples в первой строке удалить, то в последнем alert будет ошибка: переменная не определена. Это потому, что переменная let всегда видна именно в том блоке, где объявлена, и не более.
Переменная let видна только после объявления
Как мы помним, переменные var существуют и до объявления. Они равны undefined. С переменными let всё проще. До объявления их вообще нет. Заметим также, что переменные let нельзя повторно объявлять.
При использовании в цикле, для каждой итерации создаётся своя переменная
Переменная var - одна на все итерации цикла и видна даже после цикла:
for(var i = 0; i < 10; i++) { /* … */ } alert(i); // 10
С переменной let - всё по-другому.
Каждому повторению цикла соответствует своя независимая переменная let. Если внутри цикла есть вложенные объявления функций, то в замыкании каждой будет та переменная, которая была при соответствующей итерации.
- const
Ключевое слово const также позволяет создать переменную уровня блока, но такая переменная обязательно должна иметь значение и не доступна для перезаписи.
const MY_CONSTANT = 1; MY_CONSTANT = 2; // Error const SOME_CONST; // Error
Но если значением переменной является массив или объект - отдельные свойства или элементы массива можно редактировать.
Стрелочные функции (Arrow functions)
const sum = (a, b) => a + b const cube = a => a ** 3 console.log(sum(2, 3)) // 5 console.log(cube(2)) // 8
Использование стрелочных функций в определенных случаях делает код более красивым и лаконичным.
В примере ниже один и тот же код оформлен в виде стрелочной функции и обычной функции ES5:
let books = [{title: 'X', price: 10}, {title: 'Y', price: 15}]; let titles = books.map( item => item.title ); // ES5 equivalent: var titles = books.map(function(item) { return item.title; });
В синтаксисе стрелочной функции нет ключевого слова function, сначала указывается список параметров, если необходимо, затем "толстая стрелка" и далее тело функции. Если параметров нет, указываются пустые круглые скобки:
// No arguments books.map( () => 1 ); // [1, 1] // Multiple arguments [1,2].map( (n, index) => n * index ); // [0, 2]
Если тело функции содержит больше одного выражения, его необходимо поместить в фигурные скобки:
let result = [1, 2, 3, 4, 5].map(n => { n = n % 3; return n; });
Стрелочные функции отличаются от обычных не только более коротким синтаксисом. Стрелочная функуция наследует значения this и arguments от окружающего контекста. Это означает, что вам не нужно писать уродливое var that = this, контекст выполнения будет правильным. Ниже пример на ES6 и аналог на ES5:
let book = { title: 'X', sellers: ['A', 'B'], printSellers() { this.sellers.forEach(seller => console.log(seller + ' sells ' + this.title)); } } // ES5 equivalent: var book = { title: 'X', sellers: ['A', 'B'], printSellers: function() { var that = this; this.sellers.forEach(function(seller) { console.log(seller + ' sells ' + that.title) }) } }
Параметры по умолчанию
В ES6 стало возможным указание значений по умолчанию для аргументов функции:
function doSomething(x, y = 2) { return x * y; } doSomething(5); // 10 doSomething(5, undefined); // 10 doSomething(5, 3); // 15
Выглядит неплохо, правда? Аналогичный функционал на ES5:
function doSomething(x, y) { y = y === undefined ? 2 : y; return x * y; }
Rest-параметры
Rest-параметры очень похожи на оператор распространения.
Они также используют многоточие и позволяют передать в функцию произвольное количество параметров:
function doSomething(x, ...remaining) { return x * remaining.length; } doSomething(5, 0, 0, 0); // 15
Оператор распространения (Spread operator)
Оператор распространения помогает представить массив или любой итерируемый объект в виде списка элементов.
Следующий пример наглядно демонстрирует работу этого оператора:
let values = [1, 2, 4]; let some = [...values, 8]; // [1, 2, 4, 8] let more = [...values, 8, ...values]; // [1, 2, 4, 8, 1, 2, 4]
Оператор распространения крайне полезен при передаче параметров в функцию, например:
let values = [1, 2, 4]; doSomething(...values); function doSomething(x, y, z) { // x = 1, y = 2, z = 4 }
Деструктуризация (Destructuring)
Деструктуризация предоставляет удобный способ получения данных из объектов и массивов.
Рассмотрим пример с массивом:
let [x, y] = [1, 2]; // x = 1, y = 2 // ES5 equivalent: var arr = [1, 2]; var x = arr[0]; var y = arr[1];
Такой сиснтаксис позволяет присвоить значение нескольким переменным одной строкой, а также может быть полезен, если нужно поменять местами значения переменных:
let x = 1, y = 2; [x, y] = [y, x]; // x = 2, y = 1
Деструктуризация также работает для объектов, но названия ключей должны совпадать с названием полей объекта:
let obj = {x: 1, y: 2}; let {x, y} = obj; // x = 1, y = 2
Этот механизм можно использовать для изменения имен переменных:
let obj = {x: 1, y: 2}; let {x: a, y: b} = obj; // a = 1, b = 2
Другой интересный шаблон использования деструктуризации - возврат нескольких значений из функции:
function doSomething() { return [1, 2] } let [x, y] = doSomething(); // x = 1, y = 2
Деструктуризация может использоваться для присвоения аргументам функции значений по умолчанию:
function doSomething({y = 1, z = 0}) { console.log(y, z); } doSomething({y: 2})
Строки
- Методы
Несколько полезных методов были добавлены к типу String:
'my string'.startsWith('my'); //true 'my string'.endsWith('my'); // false 'my string'.includes('str'); // true 'my '.repeat(3); // 'my my my ' ' my '.trim(); // 'my' ' my '.trimStart(); // 'my ' ' my '.trimEnd(); // ' my'
- Литерал шаблона (Template literal)
Литерал шаблона позволяет использовать переменные внутри строки, заключенные в символы ${...}, например:
let name = 'John', apples = 5, pears = 7, bananas = function() { return 3; } console.log(`This is ${name}.`); console.log(`He carries ${apples} apples, ${pears} pears, and ${bananas()} bananas.`); // ES5 equivalent: console.log('He carries ' + apples + ' apples, ' + pears + ' pears, and ' + bananas() +' bananas.');
Литерал шаблона также можно использовать для многострочных строк (не забывайте, что пробел также является частью строки):
let x = `1... 2... 3 lines long!`; // ES5 equivalents: var x = "1...\n" + "2...\n" + "3 lines long!"; var x = "1...\n2...\n3 lines long!";
Массивы
Массивы также получили несколько полезных методов.
Метод Array.from создает массивы из объектов, похожих на массивы или итерируемых объектов.
Примерами объектов, похожих на массив, являются:
let itemElements = document.querySelectorAll('.items'); let items = Array.from(itemElements); items.forEach(function(element) { console.log(element.nodeType) }); // A workaround often used in ES5: let items = Array.prototype.slice.call(itemElements);
В примере выше метод forEach доступен в коллекции itemElements.
Интересной особенностью метода Array.from является второй аргумент mapFunction. Он позволяет преобразовать каждый элемент при создании массива:
let navElements = document.querySelectorAll('nav li'); let navTitles = Array.from(navElements, el => el.textContent);
Также теперь у нас есть метод Array.of, функционал которого очень похож на конструктор массива.
Он отличается для случая с одним параметром:
let x = new Array(3); // [undefined, undefined, undefined] let y = Array.of(8); // [8] let z = [1, 2, 3]; // Array literal
Несколько новых методов были добавлены прототипу Array.
[5, 1, 10, 8].find(n => n === 10) // 10 [5, 1, 10, 8].findIndex(n => n === 10) // 2 [0, 0, 0].fill(7) // [7, 7, 7] [0, 0, 0, 0, 0].fill(7, 1, 3) // [0, 7, 7, 7, 0]
Math
Несколько новых методов появились у объекта Math. В частности:
Math.sign(5); // 1 Math.sign(-9); // -1 Math.trunc(5.9); // 5 Math.trunc(5.123); // 5 Math.cbrt(64); // 4
Модули
Модули - это, пожалуй, одно из самых долгожданных нововведений в JavaScript. Ни один серьезный JavaScript-проект сегодня не обходится без какой-либо модульной системы - это может быть просто "шаблон модуль" или такие форматы, как AMD или CommonJS. Тем не менее, браузеры до недавнего времени не располагали какой-то модульной системой, т.е. необходимо было настроить сборку или загрузчик модулей AMD или CommonJS с помощью RequireJS, Browserify или Webpack.
Спецификация ES6 содержит и новый синтаксис, и механизм загрузки модулей. Рассмотрим синтаксис модулей ES6:
// lib/math.js export function sum(x, y) { return x + y; } export var pi = 3.141593; // app.js import { sum, pi } from "lib/math"; console.log('2π = ' + sum(pi, pi));
Как вы можете видеть, модуль может содержать несколько выражений export (в примере выше экспортируется функция и переменная). Выражение import в примере имеет синтаксис, схожий с деструктуризацией - в данном случае явно указывается, что именно импортируется из модуля. Чтобы импортировать весь модуль, можно использовать символ *, а ключевое слово as позволяет задать локальное имя модуля:
// app.js import * as math from "lib/math"; console.log('2π = ' + math.sum(math.pi, math.pi));
Особенностью модульной системы является экспорт по умолчанию. Для этого используется ключевое слово default. Чтобы импортировать значение по умолчанию, достаточно указать локальное имя:
// lib/my-fn.js export default function() { console.log('echo echo'); } // app.js import doSomething from 'lib/my-fn'; doSomething();
Обратите внимание, что выражение import является синхронным, т.е. код модуля не выполнится, пока не загрузятся все зависимости.
Классы
Классы - это одно из самых обсуждаемых нововведений ES6. Одни считают, что они противоречат прототипной природе JavaScript, другие - что они снижают порог вхождения, а также помогают людям, переходящим с других языков программирования. В любом случае, классы теперь являются частью стандарта ES6.
В основе классов лежат ключевые слова class и constructor. Небольшой пример:
class Vehicle { constructor(name) { this.name = name; this.kind = 'vehicle'; } getName() { return this.name; } } // Create an instance let myVehicle = new Vehicle('rocky');
Обратите внимание, что определение класса отличается от объекта, между членами класса нет запятых.
Чтобы создать экземпляр класса, вы можете воспользоваться ключевым словом new. Чтобы унаследоваться от другого класса, вы можете использовать ключевое слово extends:
class Car extends Vehicle { constructor(name) { super(name); this.kind = 'car' } } let myCar = new Car('bumpy'); myCar.getName(); // 'bumpy' myCar instanceof Car; // true myCar instanceof Vehicle; //true
В производном классе вы можете вызвать конструктор или любой метод базового класса с помощью super:
Символы
Символы - это новый примитивный тип, как Number или String.
Вы можете использовать символы для создания уникальных идентификаторов свойств объекта или униальных констант:
const MY_CONSTANT = Symbol(); let obj = {}; obj[MY_CONSTANT] = 1;
Обратите внимание, что пары ключ-значение, заданные с помощью символов, не возвращаются методами Object.getOwnPropertyNames(), Object.keys() или JSON.stringify() и не доступны в цикле for...in. Этим они отличаются от обычных строковых ключей. Получить список символов массива можно с помощью метода Object.getOwnPropertySymbols().
Из-за своей неизменяемой природы символы прекрасно работают с const:
const CHINESE = Symbol(); const ENGLISH = Symbol(); const SPANISH = Symbol(); switch(language) { case CHINESE: // break; case ENGLISH: // break; case SPANISH: // break; default: // break; }