Если вы пишете JavaScript сегодня, стоит потратить время на то, чтобы быть в курсе всех обновлений языка за последние несколько лет. С 2015 года, с выпуском ES6, каждый год выпускается новая версия спецификации ECMAScript. Каждая итерация добавляет в язык новые функции, новый синтаксис и улучшения качества жизни. Механизмы JavaScript в большинстве браузеров и Node.js быстро догоняют, и будет справедливо, если ваш код тоже догонит. Это потому, что с каждой новой итерацией JavaScript появляются новые идиомы и новые способы выражения вашего кода, и во многих случаях эти изменения могут сделать код более удобным для вас и ваших сотрудников.
Вот некоторые из последних функций ECMAScript, а также JavaScript и Node.js, которые вы можете использовать, чтобы писать более чистый, лаконичный и читаемый код.
1. Заблокируйте объявления с оценкой
С момента создания языка разработчики JavaScript использовали var
для объявления переменных. У ключевого слова var
есть свои причуды, самая проблемная из которых - это область действия переменных, созданных с его помощью.
var x = 10
if (true) {
var x = 15 // inner declaration overrides declaration in parent scope
console.log(x) // prints 15
}
console.log(x) // prints 15
Поскольку переменные, определенные с помощью var
, не имеют блочной области видимости, их переопределение в более узкой области влияет на значение внешней области.
Теперь у нас есть два новых ключевых слова, которые заменяют var
, а именно let
и const
, которые не страдают этим недостатком.
let y = 10
if (true) {
let y = 15 // inner declaration is scoped within the if block
console.log(y) // prints 15
}
console.log(y) // prints 10
const
и let
отличаются семантикой: переменные, объявленные с помощью const
, не могут быть переназначены в их области действия. Это не означает, что они неизменяемы, только то, что их ссылки не могут быть изменены.
const x = []
x.push("Hello", "World!") x // ["Hello", "World!"]
x = [] // TypeError: Attempted to assign to readonly property.
2. Стрелочные функции
Стрелочные функции - еще одна очень важная функция, недавно появившаяся в JavaScript. У них много преимуществ. Прежде всего, они делают функциональные аспекты JavaScript красивыми и удобными для написания.
let x = [1, 2, 3, 4]
x.map(val => val * 2) // [2, 4, 6, 8] x.filter(val => val % 2 == 0) // [2, 4] x.reduce((acc, val) => acc + val, 0) // 10
Во всех приведенных выше примерах стрелочные функции, названные в честь характерной стрелки =>
, заменяют традиционные функции кратким синтаксисом.
- Если тело функции представляет собой одно выражение, подразумеваются скобки области
{}
иreturn
, и их не нужно записывать. - Если функция имеет единственный аргумент, подразумеваются круглые скобки
()
аргументов, и их не нужно записывать. - Если выражение тела функции является словарем, оно должно быть заключено в круглые скобки
()
.
Еще одно важное преимущество стрелочных функций состоит в том, что они не определяют область видимости, а скорее существуют в родительской области. Это позволяет избежать множества ошибок, которые могут возникнуть при использовании ключевого слова this
. Стрелочные функции не привязаны к this
. Внутри стрелочной функции значение this
такое же, как и в родительской области. Следовательно, стрелочные функции нельзя использовать в качестве методов или конструкторов. Стрелочные функции не работают с apply
, bind
или call
и не имеют привязки к super
.
У них также есть некоторые другие ограничения, такие как отсутствие объекта arguments
, к которому могут обращаться традиционные функции, и невозможность yield
из тела функции.
Таким образом, стрелочные функции не являются заменой 1: 1 стандартным функциям, а являются желанным дополнением к набору функций JavaScript.
3. Необязательное связывание
Представьте себе глубоко вложенную структуру данных, подобную этому объекту person
здесь. Предположим, вы хотели получить доступ к имени и фамилии этого человека. Вы бы написали это на JavaScript так:
person = {
name: {
first: 'John',
last: 'Doe',
},
age: 42
}
person.name.first // 'John'
person.name.last // 'Doe'
Теперь представьте, что произошло бы, если бы объект person
не содержал вложенного объекта name
.
person = {
age: 42
}
person.name.first // TypeError: Cannot read property 'first' of undefined
person.name.last // TypeError: Cannot read property 'last' of undefined
Чтобы избежать таких ошибок, разработчикам пришлось прибегнуть к следующему коду, который излишне многословен, труден для чтения и неприятен для написания - очень плохая тройка прилагательных.
person && person.name && person.name.first // undefined
Встречайте опциональную цепочку, новую функцию JavaScript, которая устраняет это чудовище. Необязательная цепочка прерывает процесс копания, как только он встречает значение null
или undefined
, и возвращает undefined
, не вызывая ошибки.
person?.name?.first // undefined
Результирующий код намного лаконичнее и чище.
4. Нулевое слияние
Перед тем, как ввести нулевой оператор объединения, разработчики JavaScript использовали оператор OR ||
, чтобы вернуться к значению по умолчанию, если ввод отсутствовал. Это сопровождалось серьезной оговоркой, что даже допустимые, но ложные значения приведут к возврату к значениям по умолчанию.
function print(val) { return val || 'Missing' }
print(undefined) // 'Missing' print(null) // 'Missing'
print(0) // 'Missing' print('') // 'Missing' print(false) // 'Missing' print(NaN) // 'Missing'
В JavaScript теперь предлагается нулевой оператор объединения ??
, который предлагает лучшую альтернативу, поскольку он приводит к откату, только если предыдущее выражение имеет нулевое значение. Здесь нулевой символ относится к значениям null
или undefined
.
function print(val) { return val ?? 'Missing' }
print(undefined) // 'Missing' print(null) // 'Missing'
print(0) // 0 print('') // '' print(false) // false print(NaN) // NaN
Таким образом, вы можете быть уверены, что если ваша программа принимает ложные значения как допустимые входные данные, вам не придется в конечном итоге заменять их резервными.
5. Логическое присвоение
Допустим, вы хотите присвоить значение переменной тогда и только тогда, когда значение в настоящее время имеет нулевое значение. Логично написать это так:
if (x === null || x == undefined) {
x = y
}
Если вы знали, как работает короткое замыкание, вы могли бы заменить эти 3 строки кода более сжатой версией, используя нулевой оператор объединения.
x ?? (x = y) // x = y if x is nullish, else no effect
Здесь мы используем функцию короткого замыкания оператора объединения с нулевым значением, чтобы выполнить вторую часть x = y
, если x
имеет значение NULL. Код довольно лаконичен, но его все еще не очень легко читать или понимать. Логическое присвоение нулевого значения устраняет необходимость в таком обходном пути.
x ??= y // x = y if x is nullish, else no effect
В том же духе JavaScript также вводит операторы логического присваивания И &&=
и логического присваивания ИЛИ ||=
. Эти операторы выполняют присваивание только при выполнении определенного условия и не имеют никакого эффекта в противном случае.
x ||= y // x = y if x is falsy, else no effect
x &&= y // x = y if x is truthy, else no effect
Совет от профессионала: если вы писали ранее на Ruby, вы видели операторы ||=
и &&=
, поскольку в Ruby нет концепции ложных значений.
6. Именованные группы захвата.
Начнем с краткого обзора групп захвата в регулярных выражениях. Группа захвата - это часть строки, которая соответствует части регулярного выражения в круглых скобках.
let re = /(\d{4})-(\d{2})-(\d{2})/ let result = re.exec('Pi day this year falls on 2021-03-14!')
result[0] // '2020-03-14', the complete match result[1] // '2020', the first capture group result[2] // '03', the second capture group result[3] // '14', the third capture group
Регулярные выражения также уже довольно давно поддерживают именованные группы захвата, что позволяет ссылаться на группы захвата по имени, а не по индексу. Теперь, с ES9, эта функция перешла в JavaScript. Теперь объект результата содержит объект вложенных групп, в котором значение каждой группы захвата сопоставляется с ее именем.
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/ let result = re.exec('Pi day this year falls on 2021-03-14!')
result.groups.year // '2020', the group named 'year' result.groups.month // '03', the group named 'month' result.groups.day // '14', the group named 'day'
Новый API прекрасно работает с другой новой функцией JavaScript - деструктурированными назначениями.
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/ let result = re.exec('Pi day this year falls on 2021-03-14!') let { year, month, day } = result.groups
year // '2020' month // '03' day // '14'
7. async
& await
Один из мощных аспектов JavaScript - его асинхронность. Это означает, что многие функции, которые могут выполняться долго или отнимать много времени, могут возвращать Promise, а не блокировать выполнение.
const url = 'https://the-one-api.dev/v2/book' let prom = fetch(url) prom // Promise {<pending>}
// wait a bit prom // Promise {<fullfilled>: Response}, if no errors // or prom // Promise {<rejected>: Error message}, if any error
Вызов fetch возвращает обещание, которое при создании имеет статус «ожидающий». Вскоре, когда API возвращает ответ, он переходит в состояние «выполнено», и отклик, который он обертывает, становится доступным. В мире обещаний вы бы сделали что-то подобное, чтобы вызвать API и проанализировать ответ как JSON.
const url = 'https://the-one-api.dev/v2/book'
let prom = fetch(url)
prom // Promise {<fullfilled>: Response}
.then(res => res.json())
.then(json => console.log(json)) // prints response, if no errors
.catch(err => console.log(err)) // prints error message, if any error
В 2017 году JavaScript объявил о двух новых ключевых словах async
и await
, которые упрощают работу с обещаниями и работу с ними. Они не заменяют обещания; они просто синтаксический сахар поверх мощных концепций Promises.
Вместо того, чтобы весь код происходил внутри серии функций «затем», await
делает все это похожим на синхронный JavaScript. В качестве дополнительного преимущества вы можете использовать try...catch
с await
вместо обработки ошибок в функциях «catch», как если бы вы использовали Promises напрямую. Тот же код с await
будет выглядеть так.
const url = 'https://the-one-api.dev/v2/book'
let res = await fetch(url) // Promise {<fullfilled>: Response} -await-> Response
try {
let json = await res.json()
console.log(json) // prints response, if no errors
} catch(err) {
console.log(err) // prints error message, if any error
}
Ключевое слово async
- это другая сторона той же медали, поскольку оно обертывает любые данные, которые должны быть отправлены в Promise. Рассмотрим следующую асинхронную функцию для сложения нескольких чисел. В реальном мире ваш код делал бы что-то гораздо более сложное.
async function sum(...nums) { return nums.reduce((agg, val) => agg + val, 0) }
sum(1, 2, 3) // Promise {<fulfilled>: 6} .then(res => console.log(res) // prints 6
let res = await sum(1, 2, 3) // Promise {<fulfilled>: 6} -await-> 6 console.log(res) // prints 6
Эти новые функции - лишь верхушка айсберга. Мы даже не коснулись поверхности. JavaScript постоянно развивается, и каждый год в язык добавляются новые функции. Трудно идти в ногу с постоянным потоком новых функций и идиом, вводимых в язык вручную.
Первоначально опубликовано в Блоге DeepSource.