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

Кроме того, эти вещи можно использовать уже сейчас, если использовать специальные программы-транспойлеры, которые конвертируют код из нового стандарта в старый.

Команда let вместо var

Кроме команды var для объявления переменных появилась еще и команда let. Эта команда более строгая и нужна для того, чтобы ограничить область видимости переменных фигурными скобками.

Давайте посмотрим разницу между var и let. Посмотрите, как работает var: он не создает область видимости внутри if - если объявить переменную снаружи if, а потом переопределить ее внутри - снаружи эта переменная тоже поменяется:

var test = 5;
if (true) {
	var test = 10;
	alert(test); //введет 10
}

alert(test); //введет 10 - значение изменилось

А теперь посмотрим, как работает let - он создает разные области видимости снаружи и внутри if. И, если объявить переменную снаружи if, а затем попытаться затереть ее внутри - она не изменится:

let test = 5;
if (true) {
	let test = 10;
	alert(test); //введет 10
}

alert(test); //введет 5 - значение не изменилось

Команда let создает разные области видимости не только в if, но и в циклах и вообще в любых фигурных скобках.

Константы

В ES6 также появились константы. Константы - это такие переменные, значения которых нельзя изменить. Зачем нужны константы: в константу можно задать некоторое значение в начале работы скрипта - и можно будет гарантировать, что это значение в дальнейшем случайно не поменяется.

Константа объявляется командой const вместо команды var:

const test = 5;

Если попытаться сменить значение константы - мы получим ошибку:

const test = 5;
test = 10; //ошибка

Константы-объекты

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

const user = {
	name: 'Иван'
};

user.name = 'Коля'; //можно менять
user.surname = 'Иванов'; //можно

А вот затереть объект и записать туда что-нибудь другое уже не получится - вы увидите ошибку:

const user = {
	name: 'Иван'
};

user = 5; //нельзя менять, будет ошибка

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

Следующее понятие, которое появилось в ES6 называется деструктуризация. Деструктуризация - это разделение массива или объекта в отдельные переменные.

Пусть у нас дан массив ['Иванов', 'Иван']. Давайте фамилию положим в переменную surname, а имя - в переменную name. Для этого массив присвоим вот такой конструкции: var [surname, name], вот так: var [surname, name] = ['Иванов', 'Иван'].

Эта конструкция [surname, name] и есть деструктуризация. Получится, что первый элемент массива (то есть 'Иванов') запишется в переменную surname, а второй - в переменную name.

Давайте посмотрим на примере:

let [surname, name] = ['Иванов', 'Иван'];

alert(surname); //выведет 'Иванов'
alert(name); //выведет 'Иван'

Можно начать записывать в переменные не сначала массива, а пропустить некоторые значения. Давайте, к примеру, пропустим фамилию, а имя и возраст запишем в переменные. Для этого при указании переменных перед первой переменной поставим запятую, вот так: [, name, age].

Посмотрим на примере:

let [, name, age] = ['Иванов', 'Иван', '20 лет'];

alert(name); //выведет 'Иван'
alert(age); //выведет '20 лет'

Можно пропустить не одно значение, а несколько:

let [,, age] = ['Иванов', 'Иван', '20 лет'];

alert(age); //выведет '20 лет'

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

let [surname, name] = ['Иванов', 'Иван', 20, 'женат', 'без вп'];

alert(surname); //выведет 'Иванов'
alert(name); //выведет 'Иван'

Можно сохранить оставшиеся значения массива в отдельный массив. Для этого перед переменной, в которую положится остаток, следует написать троеточие. В следующем примере фамилия и имя запишутся в соответствующие переменные, а остаток массива в переменную rest:

let [surname, name, ...rest] = ['Иванов', 'Иван', 20, 'женат', 'без вп'];

alert(surname); //выведет 'Иванов'
alert(name); //выведет 'Иван'
alert(rest); //выведет [20, 'женат', 'без вп']

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

let [surname, name] = ['Иванов'];

alert(surname);
alert(name); //undefined

Для переменных можно указывать значения по умолчанию. В этом случае, если переменной не хватит элемента массива - возьмется значение по умолчанию.

В следующем примере переменной name по умолчанию указано значение 'Аноним':

let [surname, name = 'Аноним'] = ['Иванов'];

alert(surname); //введет 'Иванов'
alert(name); //введет 'Аноним'

А вот если для переменной name будет значение в массиве - значение по умолчанию будет проигнорировано:

let [surname, name = 'Аноним'] = ['Иванов', 'Иван'];

alert(surname); //введет 'Иванов'
alert(name); //введет 'Иван'

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

function getName() {
	return 'Аноним';
}

let [surname, name = getName()] = ['Иванов'];

alert(surname); //введет 'Иванов'
alert(name); //введет 'Аноним'

Интересный пример применения деструктуризации - поменяем переменные местами:

var a = 1, b = 2;
[a, b] = [b, a];

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

Кроме массивов можно также делать и деструктуризацию объектов. В следующем примере значения объекта разбиваются на соответствующие переменные (имена переменных должны совпадать в ключами объекта, порядок не имеет значения):

let options = {
	color: 'red',
	width: 400,
	height: 500
};

let {color, width, height} = options;

alert(color); //выведет 'red'
alert(width); //выведет 400
alert(height); //выведет 500

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

let options = {
	color: 'red',
	width: 400,
	height: 500
};

let {color: c, width: w, height: h} = options;

alert(c); //выведет 'red'
alert(w); //выведет 400
alert(h); //выведет 500

Можно также указывать значения по умолчанию. К примеру, укажем для цвета по умолчанию значение 'black', закомментируем элемент объекта с ключом color - и теперь в переменную color положится 'black':

let options = {
	//color: 'red',
	width: 400,
	height: 500
};

let {color = 'black', width, height} = options;

alert(color); //выведет 'black'

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

let options = {
	//color: 'red',
	width: 400,
	height: 500
};

let {color:c = 'black', width, height} = options;

alert(c);
alert(width);
alert(height);

Итераторы

В ES6 появились так называемые итераторы for-of, которые позволяют более удобно перебирать элементы массива, подобно объектам через for-in:

let arr = [1, 2, 3];

for (let value of arr) {
	alert(value); //выведет 1, 2, 3
}

Напоминаю, почему массивы нельзя перебирать через for-in - это вызовет проблемы, если с массивом поработали как с объектом и добавили в него какое-либо свойство: в этом случае это свойство также попадет в перебор (а мы этого не хотели):

var list = [1, 2, 3];
list.key = 'elem'; //поработали с массивом как с объектом

//Тут в перебор попадает 'elem':
for (var key in list) {
	console.log(list[key]); //1, 2, 3, 'elem'
}

//А тут 'elem' не попадает:
for (var value of list) {
	console.log(value); //1, 2, 3
}

Итераторы для строк

Итераторы работают и для строк - в этом случае в цикле строка будет перебираться посимвольно:

for (let symbol of 'слово') {
	alert(symbol); //выведет 'c', 'л', 'о', 'в', 'о'
}

Строки

В JavaScipt в строках не должно быть переноса строки - это приведет к ошибке:

var str = '
a
b
';

alert(str); //выдаст ошибку

Это поправили в ES6 - только строки нужно брать не в обычные кавычки, а в косые:

var str = `
a
b
`;

alert(str); //будет работать

В косых кавычках можно вставлять выражения в формате ${имя переменной} - в этом случае туда подставятся значения переменных, а также выполнятся математические операции:

let num1 = 2;
let num2 = 3;

alert(`${num1} + ${num2} = ${num1 + num2}`); //выведет '2 + 3 = 5'

Нововведения в функциях

Достаточно много нововведений коснулось функций. Давайте с ними разбираться.

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

В функциях наконец-то появились значения по умолчанию. Давайте разберем их на примере: у нас есть функция square, которая возводит число в квадрат. Сделаем так, чтобы, если параметр функции не передан, она возводила в квадрат число 3, а если передан - то переданное число:

function square(num = 3) {
	return num * num;
}

alert(square(2)); //выведет 4
alert(square()); //выведет 9

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

function square(num = Math.round(3.1)) {
	return num * num;
}

alert(square());

Деструктуризация в функциях

В параметрах функций также доступна деструктуризация.

Давайте сделаем функцию, принимающую произвольное количество параметров. Пусть в первый параметр запишется фамилия, во второй - имя, а в третий все остальные параметры в виде массива:

function func(surname, name, ...rest) {
	alert(surname); //выведет 'Иванов'
	alert(name); //выведет 'Иван'
	alert(rest); //выведет ['20 лет', 'женат', 'без вп']
}

func('Иванов', 'Иван', '20 лет', 'женат', 'без вп');

Функция также может принимать объект, элементы которого запишутся в разные переменные:

let options = {
	color: 'red',
	width: 400,
	height: 500
};

function func({color, width, height}) {
	alert(color); //выведет 'red'
	alert(width); //выведет 400
	alert(height); //выведет 500
}

func(options);

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

function func({color = 'black', width, height}) {
	
}

Также можно переименовывать переменные:

function func({color: c, width, height}) {
	
}

Ну, и можно комбинировать параметры по умолчанию и переименование:

function func({color:c = 'black', width, height}) {
	
}

Функции через =>

В ES6 появился упрощенный синтаксис функций через =>. Эта стрелка заменяет команду function и, если внутри функции только одна строка, - то return тоже не нужен - функция вернет результат выполнения этой строки.

В следующем примере вы видите функцию в новом стиле, а ниже эквивалент в старом стиле:

let func = x => x+1;
alert(func(3));

let func = function(x) { return x + 1; };
alert(func(3));

Если у функции несколько параметров - их надо брать в скобки:

let func = (x1, x2) => x1 + x2;
alert(func(3, 4));

let func = function(x1, x2) { return x1 + x2; };
alert(func(3, 4));

Если функция вообще без параметров - то нужны пустые круглые скобки:

let func = () => 3 + 4;
alert(func());

let func = function() { return 3 + 4; };
alert(func());

Если в функции несколько строк - необходим return:

let func = () => { let a = 3; let b = 4; return a + b; };
alert(func());

let func = function() { let a = 3; let b = 4; return a + b; };
alert(func());

Стрелочные функции удобно использовать в качестве анонимных:

let arr = [1, 2, 3, 4, 5];

//Старый стиль:
arr.forEach(function(value) {
alert(value);
});

//Новый стиль:
arr.forEach(value => alert(value));

Доступность this

Давайте рассмотрим некоторую проблему с this. Вы уже могли с ней сталкиваться ранее, но если вдруг не сталкивались - самое время про нее узнать.

Пусть у нас дан элемент и массив:

let arr = [1, 2, 3, 4, 5];
let elem = document.getElementById('link');

Давайте к этому элементу привяжем функцию, а в этой вызовем другую функцию, например, через forEach:

elem.onclick = function() {
	arr.forEach(function(value) {

	});
}

Все переменные, определенные выше forEach - доступны и в нем:

elem.onclick = function() {
	var test = 'aaa';	arr.forEach(function(value) {
		alert(test); //выведет 'aaa' в цикле
	});
}

Однако, это не касается this - снаружи forEach он ссылается на наш элемент, а вот внутри него this будет недоступен или будет ссылаться на объект window (так устроен this - его значение зависит от контекста):

elem.onclick = function() {
	//тут доступен this

	arr.forEach(function(value) {
		//тут this не доступен
	});
}

Чтобы сделать this доступным внутри вложенной функции обычно поступают так: записывают его в какую-либо переменную снаружи вложенной функции (переменную можно назвать как угодно, обычно это that или self). Получается, что внутри вложенной функции наш this будет доступен как that:

elem.onclick = function() {
	var that = this;
	arr.forEach(function(value) {
		//наш this доступен как that
	});
}

В вот в стрелочных функциях нет таких проблем нет - контекст выполнения не меняется и this без всяких ухищрений доступен и внутри функции:

link.onclick = function() {
	arr.forEach(elem => alert(this.innerHTML + elem));
}