ECMAScript 6 (ES6): что нового в новой версии JavaScript

Добавлено 31 октября 2015 в 10:45

Вы уже, наверное, слышали о ECMAScript 6 (или ES6). Это новая версия JavaScript, в которую добавлен новый функционал. Эти новые возможности имеют различную степень сложности и могут быть полезны и для простых скриптов, и для сложных приложений. В данной статье мы обсудим некоторые из новых возможностей ES6, которые можно использовать при ежедневном кодировании на JavaScript.

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

Переменные

LET

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

if(true) {
   let x = 1;
}
console.log(x); // undefined

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

for(let i = 0, l = list.length; i < l; i++) {
   // do something with list[i]
}

console.log(i); // undefined

Часто можно было бы использовать, например, переменную j для другой итерации в этой же области видимости. Но с let, вы можете смело объявлять i снова, так как переменные объявлены и доступны только внутри области видимости их собственного блока.

CONST

Существует еще один способ для объявления переменных с областью видимости, ограниченной блоком. С const вы объявляете ссылку на значение, доступную только для чтения. Значение переменной вы должны задать непосредственно при объявлении. Если вы попытаетесь изменить значение переменной или сразу не зададите значение, то получите сообщение об ошибке:

const MY_CONSTANT = 1;
MY_CONSTANT = 2 // Error
const SOME_CONST; // Error

Обратите внимание, что вы все еще можете изменять значения свойств объекта или членов массива:

const MY_OBJECT = {some: 1};
MY_OBJECT.some = 'body'; // Cool

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

Стрелочные функции – отличное дополнение к языку JavaScript. Они создают короткий и лаконичный код. В этой статье мы рассказываем о стрелочных функциях пораньше, чтобы позже воспользоваться ими в других примерах. Следующий фрагмент кода показывает стрелочную функцию в сравнении аналогичной функцией, написанной в знакомом стиле 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. Остается только ноль или более аргументов, «жирная стрелка» (=>) и выражение функции. Возвращаемое выражение добавляется неявно.

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

// без аргументов
books.map( () => 1 ); // [1, 1]

// несколько аргументов
[1,2].map( (n, index) => n * index ); // [0, 2]

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

let result = [1, 2, 3, 4, 5].map( n => {
   n = n % 3;
   return n;
});

Стрелочные функции означают не только меньшее количество напечатанных символов, но их поведение отличается от обычных функций. Выражение стрелочной функции наследует this и аргументы из окружающего контекста. Это означает, что вы можете избавиться от уродливых выражений, подобных var that = this, и вам не нужно связывать функции с правильным контекстом. Пример кода (обратите внимание на this.title в сравнении с that.title в версии 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)
      })
   }
}

Строки

Методы

К прототипу String была добавлена пара удобных методов. Большинство из них служат просто устранения обходных путей с методом indexOf() для получения того же результата:

'my string'.startsWith('my'); //true
'my string'.endsWith('my'); // false
'my string'.includes('str'); // true

Просто, но эффективно. Другой удобный метод был добавлен для создания повторяющейся строки:

'my '.repeat(3); // 'my my my '

Шаблонные литералы

Шаблонные литералы обеспечивают чистый способ для создания строк и интерполяции выражений. Возможно, вы уже знакомы с синтаксисом; он основан на знаке доллара и фигурных скобках ${...}. Шаблонные литералы заключаются в обратные кавычки. Краткий пример:

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.');

Как видно из кода выше, шаблонные литералы просто удобны для конкатенации по сравнению с ES5. Однако шаблонные литералы также можно использовать и для многострочных литералов. Имейте в виду, что пробельные символы (например, символ новой строки) являются частью строки:

let x = `1...
2...
3 lines long!`; // Yay

// ES5 equivalents:
var x = "1...\n" + 
"2...\n" +
"3 lines long!";

var x = "1...\n2...\n3 lines long!";

Массивы

Появилось несколько новых статических методов класса у объекта Array и несколько новых методов у прототипа Array.

Во-первых, Array.from создает экземпляры Array из массивоподобных и итерируемых объектов. Примеры массивоподобных объектов включают в себя:

  • arguments внутри функции;
  • nodeList, возвращенный методом document.getElementsByTageName();
  • данные новых структур Map и Set.
let itemElements = document.querySelectorAll('.items');
let items = Array.from(itemElements);
items.forEach(function(element) {
    console.log(element.nodeType)
});

// часто используемый в 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, который ведет себя так же, как конструктор Array. Он исправляет особый случай, когда в качестве аргумента передается одно число. Это делает Array.of более предпочтительным по сравнению с new Array(). Тем не менее, в большинстве случаев вы захотите использовать литералы array.

let x = new Array(3); // [undefined, undefined, undefined]
let y = Array.of(8); // [8]
let z = [1, 2, 3]; // Array literal

Последняя, но не менее важная, пара методов была добавлена для прототипа Array. Я думаю, что методы поиска будут очень кстати для большинства разработчиков JavaScript.

  • find возвращает первый элемент, для которого функция обратного вызова вернет true;
  • findIndex возвращает индекс первого элемента, для которого функция обратного вызова вернет true;
  • fill «перезаписывает» элементы массива переданным аргументом.
[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 возвращает знак числа как 1, -1 или 0;
  • Math.trunc возвращает переданное число без цифр после запятой;
  • Math.cbrt возвращает кубический корень числа.
Math.sign(5); // 1
Math.sign(-9); // -1

Math.trunc(5.9); // 5
Math.trunc(5.123); // 5

Math.cbrt(64); // 4

Если вы хотите узнать больше о новых числовых и математических возможностях в ES6, Dr. Axel Rauschmayer расскажет вам.

Оператор распространения

Оператор распространения (...) – это очень удобный синтаксис для разворачивания элементов массива в определенных местах, например, в качестве аргументов в вызовах функций. Показать несколько примеров – это, вероятно, лучший способ продемонстрировать, насколько они полезны.

Во-первых, посмотрим, как развернуть элементы массива внутри другого массива:

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]

// ES5 equivalent:
let values = [1, 2, 4];
// Перебрать, вставить, вспотеть, повторить...
// Перебрать, вставить, вспотеть, повторить...

Синтаксис распространения также удобен при вызове функций с аргументами:

let values = [1, 2, 4];

doSomething(...values);

function doSomething(x, y, z) {
   // x = 1, y = 2, z = 4
}

// ES5 equivalent:
doSomething.apply(null, values);

Как можете видеть, это спасает нас от часто используемого обхода fn.apply(). Синтаксис очень гибок, поэтому оператор распространения может использоваться в любом месте в качестве списка аргументов. Это означает, что следующий вызов дает тот же результат:

let values = [2, 4];
doSomething(1, ...values);

Мы применяем оператор распространения к массивам и аргументам. На самом деле, он может быть применен ко всем итерируемым объектам, таким как NodeList:

let form = document.querySelector('#my-form'),
   inputs = form.querySelectorAll('input'),
   selects = form.querySelectorAll('select');

let allTheThings = [form, ...inputs, ...selects];

Теперь allTheThings – это одномерный массив, содержащий элементы <form> и их дочерние элементы <input> и <select>.

Деструктурирование

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

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});

Параметры

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

Значения по умолчанию для параметров функций доступны в 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;
}

Значение по умолчанию для аргумента вызывается либо undefined, либо отсутствием аргумента.

Остаточные параметры

Ранее мы рассмотрели оператор распространения. Остаточные параметры очень похожи на него. Они также используют синтаксис ... и позволяют сохранять оставшиеся аргументы в массив:

function doSomething(x, ...remaining) {
   return x * remaining.length;
}

doSomething(5, 0, 0, 0); // 15

Модули

Модули – это, конечно, долгожданное дополнение к языку JavaScript. Я думаю, что это главное, ради чего стоит копаться в ES6.

Сегодня любой серьезный JavaScript проект использует какой-либо тип модульной системы, возможно что-то вроде шаблона «открытый модуль» или более обширные форматы AMD или CommonJS. Тем не менее, браузеры не располагают каким-либо типом модульной системы. Вам всегда необходим либо этап сборки, либо загрузчик ваших модулей AMD или CommonJS. Инструменты для обработки этого включают в себя RequireJS, Browserify и Webpack.

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

А теперь о синтаксисе модулей ES6. Модули разработаны вокруг ключевых слов export и import. Рассмотрим пример с двумя модулями сразу:

// 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. Каждое должно явно указывать тип экспортируемого значения (в этом примере function и var).

Выражение import в этом примере использует синтаксис (подобный деструктуризации), чтобы явно определить, что будет импортировано. Чтобы импортировать модуль целиком, должен быть использован символ * в сочетании с ключевым словом, чтобы дать модулю локальное имя:

// 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. Далее приведено очень краткое введение в классы JavaScript.

Классы построены вокруг ключевых слов class и constructor. Небольшой пример:

class Vehicle {
   constructor(name) {
      this.name = name;
      this.kind = 'vehicle';
   }
   getName() {
      return this.name;
   }   
}

// Создадим экземпляр
let myVehicle = new Vehicle('rocky');

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

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

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, чтобы получить доступ к базовому классу:

  • чтобы вызвать родительский конструктор, используйте super();
  • чтобы вызвать другой метод, используйте, например, super.getName().

Если вы хотите копать эту тему глубже, я рекомендую «Классы в ECMAScript 6» от Dr. Axel Rauschmayer.

Символы

Символы – новый примитивный тип данных, как, например, Number и String. Вы можете использовать символы, чтобы создать уникальные идентификаторы для свойств объекта, или чтобы создать уникальные константы.

const MY_CONSTANT = Symbol();

let obj = {};
obj[MY_CONSTANT] = 1;

Обратите внимание, что набор пар ключ-значение с символами не возвращается методом Object.getOwnPropertyNames(), и они не видны в итерациях for...in, Object.keys() или JSON.stringify(). Этим они отличаются от обычных ключей на основе строк. Вы можете получить массив символов объекта с помощью 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;
}

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

const CONST_1 = Symbol('my symbol');
const CONST_2 = Symbol('my symbol');

typeof CONST_1 === 'symbol'; // true

CONST_1 === CONST_2; // false

Хотите узнать больше о символах? На Mozilla Developer Network есть хорошая статья о них.

Транспиляция

Мы можем писать свой код на ES6 сегодня. Как уже упоминалось во введении, поддержка ES6 браузерами пока не очень обширна и сильно варьируется. Очень вероятно, что не всё из ES6 кода, который вы напишете, будет понято браузерами ваших пользователей. Вот почему мы должны конвертировать его в предыдущие версии JavaScript (ES5), которые хорошо запускаются на любом современном браузере. Это преобразование часто называют «транспиляция». Мы должны выполнять его с нашими приложениями, пока браузеры, которые мы хотим поддерживать, не начнут понимать ES6.

Начнем

Транспилировать код не сложно. Вы можете траспилировать код непосредственно из командной строки или можете включить в качестве плагина планировщик задач, например, Grunt или Gulp. Существует множество решений для транспиляции, в том числе и Babel, Traceur и TypeScript. Взгляните, например, на множество способов начать использовать ES6 с Babel (ранее «6to5»). Большинство возможностей ES6 в вашем распоряжении!

Теперь, когда, надеюсь, вы впечатлены ES6, почему бы не начать использовать его? В зависимости от особенностей, которые вы хотите использовать, и браузеров или оболочек, которые вам необходимо поддерживать (например, Node.js), вы, вероятно, хотите включить транспилятор в ваш рабочий процесс. И если вы готовы к этому, то существуют также наблюдатели за файлами, чтобы сделать ваш процесс разработки бесшовным.

Если вы начинаете с нуля, то возможно просто захотите конвертировать ваш код из командной строки (смотрите, например, документацию Babel CLI). Если вы уже используете планировщик задач (например, Grunt или Gulp), то можете добавить плагин (например, gulp-babel или babel-loader для Webpack). Для Grunt существует grunt-babel и много других плагинов, связанных с ES6. Люди, использующие Browserify, могут попробовать babelify.

Многие возможности могут быть преобразованы в ES5-совместимый код без существенных дополнительных усилий. Другие требуют дополнительных временных костылей (которые могут быть предоставлены транспилятором) и/или ухудшают производительность. А некоторые просто невозможны. Чтобы поэкспериментировать с кодом ES6 и посмотреть, как выглядит транспилированный код, вы можете использовать различные интерактивные инструменты (также известные как REPL):

Итак, что же я могу использовать?

В общем, некоторые из возможностей ES6 могут использоваться практически «свободно», например, модули, стрелочные функции, остаточные параметры и классы. Эти функции могут быть транспилированы в ES5 без больших накладных расходов. Дополнения к объектам и прототипам Array, String и Math (например, Array.from() и "it".startsWith("you")) требуют так называемых «заполнителей» (polyfills). Заполнители – временные костыли для функционала, который пока не поддерживается браузером. Вы можете загрузить заполнитель первым, и ваш код будет работать, как если бы браузер поддерживал этот функционал. и Babel, и Traceur предоставляют такие заполнители.

Смотрите таблицу совместимости ES6 от Kangax для полного обзора возможностей ES6, которые поддерживаются транспиляторами и браузерами. Мотивирует то, что на момент написания статьи, последние браузеры уже поддерживают от 55% до более 70% всех возможностей ES6. Microsoft Edge, Google Chrome и Mozilla Firefox здесь действительно конкурируют друг с другом, и это хорошо для веба в целом.

Лично я думаю, что возможность легко использовать новые возможности ES6, такие как модули, стрелочные функции и остаточные параметры, – это облегчение и значительное улучшение процесса кодирования.

Что дальше?

После того, как вы установите транспилятор, вы вероятно захотите начать использовать «маленькие» нововведения, такие как let и стрелочные функции. Имейте в виду, что код, который уже написан для ES5, будет нетронут транспилятором. Как только вы улучшите свои скрипты с ES6 и получите от этого удовольствие, то начнете постепенно добавлять в код всё больше и больше возможностей ES6. Возможность преобразовать какой-либо код в новые модули или синтаксис классов. Я обещаю, что это будет круто!

В ES6 гораздо больше возможностей, чем мы смогли охватить в этой статье. Неохваченные возможности включают в себя Map, Set, тегированные шаблонные строки, генераторы, Proxy и Promise.

Заключение

При использовании транспилятора весь ваш код эффективно «заключается» в ES5, пока браузеры будут продолжать добавлять новые возможности. Таким образом, даже если браузер полностью поддерживает определенную возможность ES6, ES5-совместимая версия, которая будет использоваться, возможно будет хуже по производительности. Вы можете рассчитывать на то, что любая возможность ES6, и в конечном счете все из них, будут поддерживаться в определенном месте (в браузерах и средах, которые вам необходимо поддерживать на тот момент). Но до тех пор мы должны выборочно отключать возможности ES6 от транспиляции в ES5, чтобы предотвратить излишнюю нагрузку. Имея это в виду, решите для себя, подходит ли данный момент, чтобы начать использовать ES6 (какие-либо его части).


На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.


Сообщить об ошибке