JavaScript сильно изменился за последние годы. Вот 12 новых возможностей, которые можно начать использовать уже сегодня!
История
Новые добавления в язык называются ECMAScript 6. Или ES6 или ES2015+.
С момента появления в 1995, JavaScript развивался медленно. Новые возможности добавлялись каждые несколько лет. ECMAScript появился в 1997, его целью было направить развитие JavaScript в нужное русло. Выходили новые версии – ES3, ES5, ES6 и так далее.

Как видите, между версиями ES3, ES5 и ES6 есть пропуски длиной в 10 и 6 лет. Новая модель – делать маленькие изменения каждый год. Вместо того, чтобы накопить огромное количество изменений и выпустить их все за раз, как это было с ES6.
Browsers Support
Все современные браузеры и среды исполнения уже поддерживают ES6!

Chrome, MS Edge, Firefox, Safari, Node и многие другие системы имеют встроенную поддержку большинства возможностей JavaScript ES6. Так что, все из этого пособия можно использовать прямо сейчас.
Поехали!
Главные возможности ES6
Все сниппеты можно вставлять в консоль браузера и запускать.
Block scope variables
В ES6 мы перешли от var к let/const.
Что не так с var?
Проблема var в том, что переменная "протекает" в другие блоки кода, такие как циклы for или блоки условий if:
ES5
var x = 'outer';
function test(inner) {
if (inner) {
var x = 'inner'; // scope whole function
return x;
}
return x; // gets redefined on line 4
}
test(false); // undefined
test(true); // innerВ строке test(false) можно ожидать возврат outer, но нет, мы получаем undefined. Почему?
Потому что даже не смотря на то, что блок if не выполняется, на 4й строке происходит переопределение var x как undefined.
ES6 спешит на помощь:
ES6
let x = 'outer';
function test(inner) {
if (inner) {
let x = 'inner';
return x;
}
return x; // gets result from line 1 as expected
}
test(false); // outer
test(true); // innerИзменив var на let мы откорректировали поведение. Если блок if не вызывается, то переменная x не переопределяется.
IIFE (immediately invoked function expression)
Давайте сначала рассмотрим пример:
ES5
{
var private = 1;
}
console.log(private); // 1Как видите, private протекает наружу. Нужно использовать IIFE (immediately-invoked function expression):
ES5
(function(){
var private2 = 1;
})();
console.log(private2); // Uncaught ReferenceErrorЕсли взглянуть на jQuery/lodash или любые другие проекты с открытым исходным кодом, то можно заметить, что там IIFE используется для содержания глобальной среды в чистоте. А глобальные штуки определяются со специальными символами вроде _, $ или jQuery.
В ES6 не нужно использовать IIFE, достаточно использовать блоки и let:
ES6
{
let private3 = 1;
}
console.log(private3); // Uncaught ReferenceErrorConst
Можно также использовать const если переменная не должна изменяться.
Итог:
- забудьте
var, используйтеletиconst. - Используйте
constдля всех референсов; не используйтеvar. - Если референсы нужно переопределять, используйте
letвместоconst.
Template Literals
Не нужно больше делать вложенную конкатенацию, можно использовать шаблоны. Посмотрите:
ES5
var first = 'Adrian';
var last = 'Mejia';
console.log('Your name is ' + first + ' ' + last + '.');С помощью бэктика () и интерполяции строк ${}` можно сделать так:
ES6
const first = 'Adrian';
const last = 'Mejia';
console.log(`Your name is ${first} ${last}.`);Multi-line strings
Не нужно больше конкатенировать строки с + \n:
ES5
var template = '<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >\n' +
' <div class="view">\n' +
' <input class="toggle" type="checkbox" [checked]="todo.isDone">\n' +
' <label></label>\n' +
' <button class="destroy"></button>\n' +
' </div>\n' +
' <input class="edit" value="">\n' +
'</li>';
console.log(template);В ES6 можно снова использовать бэктики:
ES6
const template = `<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >
<div class="view">
<input class="toggle" type="checkbox" [checked]="todo.isDone">
<label></label>
<button class="destroy"></button>
</div>
<input class="edit" value="">
</li>`;
console.log(template);Оба блока кода генерируют одинаковый результат
Destructuring Assignment
ES6 destructing – полезная и лаконичная штука. Посмотрите на примеры:
Получение элемента из массива
ES5
var array = [1, 2, 3, 4];
var first = array[0];
var third = array[2];
console.log(first, third); // 1 3То же самое:
ES6
const array = [1, 2, 3, 4];
const [first, ,third] = array;
console.log(first, third); // 1 3Обмен значениями
ES5
var a = 1;
var b = 2;
var tmp = a;
a = b;
b = tmp;
console.log(a, b); // 2 1То же самое:
ES6
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1Деструктуризация нескольких возвращаемых значений
ES5
function margin() {
var left=1, right=2, top=3, bottom=4;
return { left: left, right: right, top: top, bottom: bottom };
}
var data = margin();
var left = data.left;
var bottom = data.bottom;
console.log(left, bottom); // 1 4В строке 3 можно вернуть в виде массива:
return [left, right, top, bottom];но вызывающему коду придется знать о порядке данных.
var left = data[0];
var bottom = data[3];С ES6 вызывающий выбирает только нужные данные (строка 6):
ES6
function margin() {
const left=1, right=2, top=3, bottom=4;
return { left, right, top, bottom };
}
const { left, bottom } = margin();
console.log(left, bottom); // 1 4Заметка: В строке 3 содержатся другие возможности ES6. Можно сократить { left: left } до { left }. Смотрите, насколько это лаконичнее по сравнению с версией ES5. Круто же?
Деструктуризация и сопоставление параметров
ES5
var user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName(user) {
var firstName = user.firstName;
var lastName = user.lastName;
return firstName + ' ' + lastName;
}
console.log(getFullName(user)); // Adrian MejiaТо же самое (но короче):
ES6
const user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
console.log(getFullName(user)); // Adrian MejiaГлубокое сопоставление
ES5
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
var tmp = settings();
var displayColor = tmp.display.color;
var keyboardLayout = tmp.keyboard.layout;
console.log(displayColor, keyboardLayout); // red quertyТо же самое (но короче):
ES6
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();
console.log(displayColor, keyboardLayout); // red quertyЭто также называют деструктуризацией объекта (object destructing).
Как видите, деструктуризация может быть очень полезной и может подталкивать к улучшению стиля кодирования.
Советы:
- Используйте деструктуризацию для получения элементов из массива и для обмена значениями. Не нужно делать временные референсы – сэкономите время.
- Не используйте деструктуризацию массива для нескольких возвращаемых значений, вместо этого используйте деструктуризацию объекта.
Классы и объекты
В ECMAScript 6 мы перешли от “функций-конструкторов” к “классам” .
Каждый объект в JavaScript имеет прототип, который является другим объектом. Все объекты в JavaScript наследуют методы и свойства от своего прототипа.
В ES5 объектно-ориентированное программирование достигалось с помощью функций-конструкторов. Они создавали объекты следующим образом:
ES5
var Animal = (function () {
function MyConstructor(name) {
this.name = name;
}
MyConstructor.prototype.speak = function speak() {
console.log(this.name + ' makes a noise.');
};
return MyConstructor;
})();
var animal = new Animal('animal');
animal.speak(); // animal makes a noise.В ES6 есть новый синтаксический сахар. Можно сделать то же самое с меньшим кодом и с использованием ключевых слов class и construсtor. Также заметьте, как четко определяются методы: construсtor.prototype.speak = function () vs speak():
ES6
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
const animal = new Animal('animal');
animal.speak(); // animal makes a noise.Оба стиля (ES5/6) дают одинаковый результат.
Советы:
- Всегда используйте синтаксис
classи не изменяйтеprototypeнапрямую. Код будет лаконичнее и его будет легче понять. - Избегайте создания пустого конструктора. У классов есть конструктор по умолчанию если не задать собственный.
Наследование
Давайте продолжим предыдущий пример с классом Animal. Допустим, нам нужен новый класс Lion.
В ES5 придется немного поработать с прототипным наследованием.
ES5
var Lion = (function () {
function MyConstructor(name){
Animal.call(this, name);
}
// prototypal inheritance
MyConstructor.prototype = Object.create(Animal.prototype);
MyConstructor.prototype.constructor = Animal;
MyConstructor.prototype.speak = function speak() {
Animal.prototype.speak.call(this);
console.log(this.name + ' roars ');
};
return MyConstructor;
})();
var lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.Не будем вдаваться в детали, но заметьте несколько деталей:
- Строка 3, напрямую вызываем конструктор
Animalс параметрами. - Строки 7-8, назначаем прототип
Lionпрототипом классаAnimal. - Строка 11, вызываем метод
speakиз родительского классаAnimal.
В ES6 есть новые ключевые слова extends и super.
ES6
class Lion extends Animal {
speak() {
super.speak();
console.log(this.name + ' roars ');
}
}
const lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.Посмотрите, насколько лучше выглядит код на ES6 по сравнению с ES5. И они делают одно и то же! Win!
Совет:
- Используйте встроенный способ наследования –
extends.
Нативные промисы
Переходим от callback hell к промисам (promises)
ES5
function printAfterTimeout(string, timeout, done){
setTimeout(function(){
done(string);
}, timeout);
}
printAfterTimeout('Hello ', 2e3, function(result){
console.log(result);
// nested callback
printAfterTimeout(result + 'Reader', 2e3, function(result){
console.log(result);
});
});Одна функция принимает callback чтобы запустить его после завершения. Нам нужно запустить ее дважды, одну за другой. Поэтому приходится вызывать printAfterTimeout во второй раз в коллбеке.
Все становится совсем плохо когда нужно добавить третий или четвертый коллбек. Давайте посмотрим, что можно сделать с промисами:
ES6
function printAfterTimeout(string, timeout){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(string);
}, timeout);
});
}
printAfterTimeout('Hello ', 2e3).then((result) => {
console.log(result);
return printAfterTimeout(result + 'Reader', 2e3);
}).then((result) => {
console.log(result);
});С помощью then можно обойтись без вложенных функций.
Стрелочные функции
В ES5 обычные определения функций не исчезли, но был добавлен новый формат – стрелочные функции.
В ES5 есть проблемы с this:
ES5
var _this = this; // need to hold a reference
$('.btn').click(function(event){
_this.sendData(); // reference outer this
});
$('.input').on('change',function(event){
this.sendData(); // reference outer this
}.bind(this)); // bind to outer thisНужно использовать временный this чтобы ссылаться на него внутри функции или использовать bind. В ES6 можно просто использовать стрелочную функцию!
ES6
// this will reference the outer one
$('.btn').click((event) => this.sendData());
// implicit returns
const ids = [291, 288, 984];
const messages = ids.map(value => `ID is ${value}`);For…of
От for переходим к forEach а потом к for...of:
ES5
// for
var array = ['a', 'b', 'c', 'd'];
for (var i = 0; i < array.length; i++) {
var element = array[i];
console.log(element);
}
// forEach
array.forEach(function (element) {
console.log(element);
});ES6 for…of позволяет использовать итераторы
ES6
// for ...of
const array = ['a', 'b', 'c', 'd'];
for (const element of array) {
console.log(element);
}Параметры по умолчанию
От проверки параметров переходим к параметрам по умолчанию. Вы делали что-нибудь такое раньше?
ES5
function point(x, y, isFlag){
x = x || 0;
y = y || -1;
isFlag = isFlag || true;
console.log(x,y, isFlag);
}
point(0, 0) // 0 -1 true
point(0, 0, false) // 0 -1 true
point(1) // 1 -1 true
point() // 0 -1 trueСкорее всего да. Это распространенный паттерн проверки наличия значения переменной. Но тут есть некоторые проблемы:
- Строка 8, передаем
0, 0получаем0, -1 - Строка 9, передаем
false, но получаемtrue.
Если параметр по умолчанию это булева переменная или если задать значение 0, то ничего не получится. Почему? Расскажу после этого примера с ES6 ;)
В ES6 все получается лучше с меньшим количеством кода:
ES6
function point(x = 0, y = -1, isFlag = true){
console.log(x,y, isFlag);
}
point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 trueПолучаем ожидаемый результат. Пример в ES5 не работал. Нужно проверять на undefined так как false, null, undefined и 0 – это все falsy-значения. С числами можно так:
ES5
function point(x, y, isFlag){
x = x || 0;
y = typeof(y) === 'undefined' ? -1 : y;
isFlag = typeof(isFlag) === 'undefined' ? true : isFlag;
console.log(x,y, isFlag);
}
point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 trueС прове��кой на undefined все работает как нужно.
Rest-параметры
От аргументов к rest-параметрам и операции spread.
В ES5 работать с переменным количеством аргументов неудобно.
ES5
function printf(format) {
var params = [].slice.call(arguments, 1);
console.log('params: ', params);
console.log('format: ', format);
}
printf('%s %d %.2f', 'adrian', 321, Math.PI);С rest ... все намного проще.
ES6
function printf(format, ...params) {
console.log('params: ', params);
console.log('format: ', format);
}
printf('%s %d %.2f', 'adrian', 321, Math.PI);Операция Spread
Переходим от apply() к spread. Опять же, ... спешит на помощь:
Помните: мы используемapply()чтобы превратить массив в список аргументов. например,Math.max()принимает список параметров, но если у нас есть массив, то можно использоватьapply.
ES5
Math.max.apply(Math, [2,100,1,6,43]) // 100В ES6 используем spread:
ES6
Math.max(...[2,100,1,6,43]) // 100Мы также перешли от concat к spread'у:
ES5
var array1 = [2,100,1,6,43];
var array2 = ['a', 'b', 'c', 'd'];
var array3 = [false, true, null, undefined];
console.log(array1.concat(array2, array3));В ES6:
ES6
const array1 = [2,100,1,6,43];
const array2 = ['a', 'b', 'c', 'd'];
const array3 = [false, true, null, undefined];
console.log([...array1, ...array2, ...array3]);Заключение
JavaScript сильно изменился. Эта статья покрывает только базовые возможности, о которых должен знать каждый разработчик.