Советы и приёмы ES6, которые сделают ваш код чистым и читабельным.

Шаблоны строк

Шаблоны строк дают возможность работать со строками проще, чем когда-либо. Они начинаются с ‘обратной кавычки’, и могут содержать переменные используя такую конструкцию - ${variable}. Сравните две идентичные переменные.
a - привычный нам способом конкатенации строк.
b - строка с использование шаблона строк.

const fName = 'Peter',
sName = 'Smith',
age = 43,
job= 'photographer';
const a = 'Hi, I\'m ' + fName + ' ' + sName + ', I\'m ' + age + ' and work as a ' + job + '.';
const b = `Hi, I'm ${fName} ${sName}, I'm ${age} and work as a ${job}.`;

Это делает жизнь легче, а код более читабельным. Вы можете вставлять все что угодно внутрь фигурных скобок: переменные, вычисления, вызов функций. Я буду пользоваться этой возможностью в примерах кода на протяжении этой статьи.

Синтаксическая область видимости (Syntax Block scoping)

Область видимости в JavaScript всегда была ограничена функциями, именно поэтому часто можно было встретить обёртку из самовызывающейся функции в JS файлах (IIFE). Так поступали, чтобы изолировать все переменные в файле во избежание конфликтов переменных.

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

Объявление с помощью ‘let’

Способ очень похож на var, но есть несколько важных отличий. Используя let - область видимости ограничивается блоком, новая переменная с идентичным именем может быть объявлена не затрагивая внешние переменные.

var a = 'car' ;
{
let a = 5;
console.log(a) // 5
}
console.log(a) // car

Так как подобные выражения привязаны к блочной области видимости, они решают классическую задачу на интервью: “Каков результат выполнение кода, и как сделать так, чтобы результат был таким, каким мы ожидаем?”

for (var i = 1; i < 5; i++){
setTimeout(() => { console.log(i); }, 1000);
}

В этом случае мы увидим “5 5 5 5 5”, так как переменная i изменяется на каждой итерации.

Если поменять var на let, то получим совершенно другой результат. Теперь, при каждой итерации цикл создаёт новый блок, в котором значение переменной i привязывается к конкретному блоку. Выглядит это следующим образом:

{let i = 1; setTimeout(() => { console.log(i) }, 1000)} 
{let i = 2; setTimeout(() => { console.log(i) }, 1000)}
{let i = 3; setTimeout(() => { console.log(i) }, 1000)}
{let i = 4; setTimeout(() => { console.log(i) }, 1000)}
{let i = 5; setTimeout(() => { console.log(i) }, 1000)}

Еще одно различие между var и let заключается в том, что let не подвергается хоистингу(поднятию), как это делает var.

{ 
console.log(a); // undefined
console.log(b); // ReferenceError
var a = 'car';
let b = 5;
}

Многие утверждают, что лучше использовать let , так как переменная объявленная таким образом будет иметь более предсказуемое поведение. Кроме тех специфических случаев, когда вам нужен хоистинг.

Const

Раньше, если вы хотели объявить константу(“неизменяемую переменную”), вам пришлось бы использовать соглашение о специальном названии переменной. Тем не менее такой подход не обезопасит переменную - это всего лишь позволит понять другим разработчикам, что эту переменную не стоит изменять.

Теперь у нас есть объявление переменной через const.

{
const c = "tree";
console.log(c); // tree
c = 46; // TypeError!
}

const не делает переменную иммутабельной(неизменяемой), а всего лишь “блокирует” её тип. Если у переменной более сложное назначение (объект или массив), тогда её содержимое всё ещё может изменяться.

{
const d = [1, 2, 3, 4];
const dave = { name: 'David', age: 32};
d.push(5);
dave.job = "salesman";
console.log(d); // [1, 2, 3, 4, 5]
console.log(dave); // { age: 32, job: "salesman", name: 'David'}
}

Проблемы функций в блочной области видимости

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

{
bar(); // works
function bar() { /* do something */ }
}
bar(); // doesn't work

Проблема обнаруживается, когда вы обьявляете функцию внутри условия (if {...}.

if ( something) {
function baz() { console.log('I passed') }
} else {
function baz() { console.log('I didn\'t pass') }
}
baz();

До ES6, оба обьявления были бы “подняты”, и результат был бы таким - 'I didn\'t pass', невзирая на то, что в something. Сейчас мы получим ошибку 'ReferenceError', так как baz привязан к блочной области видимсоти.

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

ES6 предоставляет оператор ..., который носит название - оператор ‘spread’. Он имеет два основных способа применения: распределение массива или объекта на новый массив или объект, и соединение нескольких параметров в массив.

С первым способом вы будете сталкиваться чаще всего, поэтому разберём его в первую очередь.

let a = [3, 4, 5];
let b = [1, 2, ...a, 6];
console.log(b); // [1, 2, 3, 4, 5, 6]

Это может быть очень полезным при отправке набора переменных в качестве аргументов для функции из массива.

function foo(a, b, c) { console.log(`a=${a}, b=${b}, c=${c}`)} 
let data = [5, 15, 2];
foo( ...data); // a=5, b=15, c=2

Объект тоже можно “распределить”, вводя каждую пару ключ-значение в новый объект. (На самом деле распределение объекта находится в четвёртой стадии рассмотрения, и будет официально представлено в ES2018)

let car = { type: 'vehicle ', wheels: 4};
let fordGt = { make: 'Ford', ...car, model: 'GT'};
console.log(fordGt); // {make: 'Ford', model: 'GT', type: 'vehicle', wheels: 4}

Еще одна особенность распределяющего оператора заключается в том, что он создает новые массив или объект. Пример ниже создаёт новый массив дляb, но c всего лишь ссылается на такой же массив.

let a = [1, 2, 3];
let b = [ ...a ];
let c = a;
b.push(4);
console.log(a); // [1, 2, 3]
console.log(b); // [1, 2, 3, 4] referencing different arrays
c.push(5);
console.log(a); // [1, 2, 3, 5]
console.log(c); // [1, 2, 3, 5] referencing the same array

Второй способ - это комплектация переменных в один массив. Это очень полезно, когда вы не знаете точного кол-ва переменных отправленных в функцию.

function foo(...args) {
console.log(args);
}
foo( 'car', 54, 'tree'); // [ 'car', 54, 'tree' ]

Значение параметров по умолчанию (Default Parameters)

Теперь функции могут быть определены с установленными по умолчанию значениями параметров. Потерянные или неопределённые (undefined) значения проинициализируются со значениями определёнными по умолчанию. Будьте аккуратны - так как значения null и false будут преобразованы в 0.

function foo( a = 5, b = 10) {
console.log( a + b);
}
foo(); // 15
foo( 7, 12 ); // 19
foo( undefined, 8 ); // 13
foo( 8 ); // 18
foo( null ); // 10 as null is coerced to 0

Значение по умолчанию могут быть не только простыми значениями - они также могут являться выражениями или функциями.

function foo( a ) { return a * 4; }
function bar( x = 2, y = x + 4, z = foo(x)) {
console.log([ x, y, z ]);
}
bar(); // [ 2, 6, 8 ]
bar( 1, 2, 3 ); //[ 1, 2, 3 ]
bar( 10, undefined, 3 ); // [ 10, 14, 3 ]

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

Деструктуризация - это особый синтаксис присваивания, при котором можно присвоить массив или объект сразу нескольким переменным, разбив его на части.

let [ a, b, c ] = [ 6, 2, 9];
console.log(`a=${a}, b=${b}, c=${c}`); //a=6, b=2, c=9
function foo() { return ['car', 'dog', 6 ]; } 
let [ x, y, z ] = foo();
console.log(`x=${x}, y=${y}, z=${z}`); // x=car, y=dog, z=6

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

function bar() { return {a: 1, b: 2, c: 3}; }
let { a, c } = bar();
console.log(a); // 1
console.log(c); // 3
console.log(b); // undefined

Иногда, вам потребуется извлечь значение из объекта, но при этом объявить его в переменную с другим именем. В этом вам поможет сочетания ‘ключ: переменная’ по левую сторону от знака равенства.

function baz() { 
return {
x: 'car',
y: 'London',
z: { name: 'John', age: 21}
};
}
let { x: vehicle, y: city, z: { name: driver } } = baz();
console.log(
`I'm going to ${city} with ${driver} in their ${vehicle}.`
); // I'm going to London with John in their car.

Литералы объектов

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

let a = 4, b = 7;
let c = { a: a, b: b };
let concise = { a, b };
console.log(c, concise) // {a: 4, b: 7}, {a: 4, b: 7}

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

function foo() {
return {
name: 'Anna',
age: 56,
job: { company: 'Tesco', title: 'Manager' }
};
}
// pre ES6
let a = foo(), name = a.name, age = a.age, company = a.job.company;
// ES6 destructuring and concise parameters 
let { name, age, job: {company} } = foo();

Так же, это можно использовать, чтобы производить деструктуризацию объектов в функции. Методы 1 м 2 показывают, как бы вы сделали это до ES6, а метод 3 использует деструктуризацию.

let person = {
name: 'Anna',
age: 56,
job: { company: 'Tesco', title: 'Manager' }
};
// method 1
function old1( person) {
var yearOfBirth = 2018 - person.age;
console.log( `${ person.name } works at ${ person.job.company } and was born in ${ yearOfBirth }.`);
}
// method 2
function old1( person) {
var age = person.age,
yearOfBirth = 2018 - age,
name = person.name,
company = person.job.company;
console.log( `${ name } works at ${ company } and was born in ${ yearOfBirth }.`);
}
// method 3
function es6({ age, name, job: {company}}) {
var yearOfBirth = 2018 - age;
console.log( `${ name } works at ${ company } and was born in ${ yearOfBirth }.`);
}

Используя ES6, мы можем извлечь age, name и company без обьявления дополнительных переменных.

Динамические имена свойств

ES6 adds the ability to create or add properties with dynamically assigned keys.

let  city= 'sheffield_';
let a = {
[ city + 'population' ]: 350000
};
a[ city + 'county' ] = 'South Yorkshire';
console.log(a); // {sheffield_population: 350000, sheffield_county: 'South Yorkshire' }

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

У стрелочных функций есть две главные особенности: структура и привязка this.

Стрелочные функции могут иметь значительно более простую структуру, чем традиционные функции, так как они не требуют ключевого слова function, и они автоматически возвращают то, что находится после стрелки.

var foo = function( a, b ) {
return a * b;
}
let bar = ( a, b ) => a * b;

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

let baz = ( c, d ) => {
let length = c.length + d.toString().length;
let e = c.join(', ');
return `${e} and there is a total length of ${length}`;
}

Самые распространённые места для использования стрелочных функций - это функции обработки массивов. Такие, как .map.forEach или .sort.

let arr = [ 5, 6, 7, 8, 'a' ];
let b = arr.map( item => item + 3 );
console.log(b); // [ 8, 9, 10, 11, 'a3' ]

Имея более лаконичиный синтаксис, стрелочные функции так же “чинят” проблему, которая часто возникала из-за поведения привязки this. Во времена до ES6, решением было хранить ссылку на this, как самостоятельную переменную self.

var clickController = {
doSomething: function (..) {
var self = this;
btn.addEventListener(
'click',
function() { self.doSomething(..) },
False
);
}
};

Этот способ не очень хорошо, так как как привязка this происходит динамически. Это значит, что this внутри “события”, и this внутри doSomething не ссылаются на одно и тоже.

Внутри стрелочных функций, привязка this является лексической, а не динамической. Что я вляется главной особенностью стрелочных функций.

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

let a = {
oneThing: ( a ) => {
let b = a * 2;
this.otherThing(b);
},
otherThing: ( b ) => {....}
};
a.oneThing(6);

Когда мы используем a.oneThing(6), попытка вызвать this.otherThing(b) выдаст ошибку, так как this не ссылает нас к объекту a, но к внешней области видимости. Если вы переписываете “legacy code” используя синтаксис ES6, тогда вам стоит остерегаться данной проблемы.

Циклы for … of

ES6 добавляет еще один способ итерации элементов массива. Он отличается от существующего цикла for ... in, который проходит по ключу/индексу.

let a = ['a', 'b', 'c', 'd' ];
// ES6
for ( var val of a ) {
console.log( val );
} // "a" "b" "c" "d"
// pre-ES6
for ( var idx in a ) {
console.log( idx );
} // 0 1 2 3

Массивы, строки, генераторы и коллекции - все они итерируемы по стандарту JavaScript. Простые объекты нельзя проитерировать стандартным методом, только если вы не добавите итератор.

И еще много всего…

Есть еще куча всего, что ES6 предлагает нам, чтобы сделать наш код чище, компактнее, более читабельным и надёжнее.


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


Источник - Sam Williams