- PVSM.RU - https://www.pvsm.ru -
Сегодня, в девятой части перевода руководства по JavaScript, будет сделан обзор возможностей, которые появились в языке благодаря стандартам ES7, ES8 и ES9.
→ Часть 1: первая программа, особенности языка, стандарты [1]
→ Часть 2: стиль кода и структура программ [2]
→ Часть 3: переменные, типы данных, выражения, объекты [3]
→ Часть 4: функции [4]
→ Часть 5: массивы и циклы [5]
→ Часть 6: исключения, точка с запятой, шаблонные литералы [6]
→ Часть 7: строгий режим, ключевое слово this, события, модули, математические вычисления [7]
→ Часть 8: обзор возможностей стандарта ES6 [8]
→ Часть 9: обзор возможностей стандартов ES7, ES8 и ES9 [9]
Стандарт ES7, который, в соответствии с официальной терминологией, называется ES2016, вышел летом 2016 года. Он, в сравнении с ES6, принёс в язык не так много нового. В частности, речь идёт о следующем:
Array.prototype.includes()
.
Метод Array.prototype.includes()
предназначен для проверки наличия в массиве некоего элемента. Находя в массиве искомое, он возвращает true
, не находя — false
. До ES7 для выполнения той же операции служил метод indexOf()
, который возвращает, в случае нахождения элемента, первый индекс, по которому его можно обнаружить в массиве. Если же indexOf()
элемента не находит — он возвращает число -1
.
В соответствии с правилами преобразования типов JavaScript число -1
преобразуется в true
. Как результат, для проверки результатов работы indexOf()
следовало пользоваться не особенно удобной конструкцией следующего вида.
if ([1,2].indexOf(3) === -1) {
console.log('Not found')
}
Если в подобной ситуации, полагая, что indexOf()
, не находя элемента, возвращает false
, воспользоваться чем-то вроде показанного ниже, код будет работать неправильно.
if (![1,2].indexOf(3)) { //неправильно
console.log('Not found')
}
В данном случае оказывается, что конструкция ![1,2].indexOf(3)
даёт false
.
С использованием метода includes()
подобные сравнения выглядят гораздо логичнее.
if (![1,2].includes(3)) {
console.log('Not found')
}
В данном случае конструкция [1,2].includes(3)
возвращает false
, это значение оператор !
превращает в true
и в консоль попадает сообщение о том, что искомый элемент в массиве не найден.
Оператор возведения в степень выполняет ту же функцию, что и метод Math.pow()
, но пользоваться им удобнее, чем библиотечной функцией, так как он является частью языка.
Math.pow(4, 2) == 4 ** 2 //true
Этот оператор можно считать приятным дополнением JS, которое пригодится в приложениях, выполняющих некие вычисления. Похожий оператор существует и в других языках программирования.
Стандарта ES8 (ES2017) вышел в 2017 году. Он, как и ES7, внёс в язык не особенно много нового. А именно, речь идёт о следующих возможностях:
Object.values()
.Object.entries()
.Object.getOwnPropertyDescriptors()
.
В ES8 появились два новых метода объекта String
— padStart()
и padEnd()
.
Метод padStart()
заполняет текущую строку другой строкой до тех пор, пока итоговая строка не достигнет нужной длины. Заполнение происходит в начале строки (слева). Вот как пользоваться этим методом.
str.padStart(targetLength [, padString])
Здесь str
— это текущая строка, targetLength
— длина итоговой строки (если она меньше длины текущей строки — эта строка будет возвращена без изменений), padString
— необязательный параметр — строка, используемая для заполнения текущей строки. Если параметр padString
не задан — для дополнения текущей строки до заданной длины используется символ пробела.
Метод padEnd()
аналогичен padStart()
, но заполнение строки происходит справа.
Рассмотрим примеры использования этих методов.
const str = 'test'.padStart(10)
const str1 = 'test'.padEnd(10,'*')
console.log(`'${str}'`) //' test'
console.log(`'${str1}'`) //'test******'
Здесь, при использовании padStart()
с указанием лишь желаемой длины итоговой строки, в начало исходной строки были добавлены пробелы. При использовании padEnd()
с указанием длины итоговой строки и строки для её заполнения в конец исходной строки были добавлены символы *
.
Этот метод возвращает массив, содержащий значения собственных свойств объекта, то есть таких свойств, которые содержит сам объект, а не тех, которые доступны ему через цепочку прототипов.
Вот как им пользоваться.
const person = { name: 'Fred', age: 87 }
const personValues = Object.values(person)
console.log(personValues) // ['Fred', 87]
Этот метод применим и к массивам.
Этот метод возвращает массив, каждый элемент которого также является массивом, содержащим, в формате [key, value]
, ключи и значения собственных свойств объекта.
const person = { name: 'Fred', age: 87 }
const personValues = Object.entries(person)
console.log(personValues) // [['name', 'Fred'], ['age', 87]]
При применении этого метода к массивам в качестве ключей выводятся индексы элементов, а в качестве значений выводится то, что хранится в массиве по соответствующим индексам.
Этот метод возвращает сведения обо всех собственных свойствах объекта. Со свойствами объектов ассоциированы наборы атрибутов (дескрипторы). В частности, речь идёт о следующих атрибутах:
value
— значение свойства объекта.writable
— содержит true
если свойство можно менять.get
— содержит функцию-геттер, связанную со свойством, или, если такой функции нет — undefined
.set
— содержит функцию-сеттер для свойства или undefined
.configurable
— если тут будет false
— свойство нельзя удалять, нельзя менять его атрибуты за исключением значения.enumerable
— если в этом свойстве будет содержаться true — свойство
является перечислимым.Вот как пользоваться этим методом.
Object.getOwnPropertyDescriptors(obj)
Он принимает объект, сведения о свойствах которого нужно узнать, и возвращает объект, содержащий эти сведения.
const person = { name: 'Fred', age: 87 }
const propDescr = Object.getOwnPropertyDescriptors(person)
console.log(propDescr)
/*
{ name:
{ value: 'Fred',
writable: true,
enumerable: true,
configurable: true },
age:
{ value: 87,
writable: true,
enumerable: true,
configurable: true } }
*/
Зачем нужен этот метод? Дело в том, что он позволяет создавать мелкие копии объектов, копируя, помимо других свойств, геттеры и сеттеры. Этого нельзя было сделать, пользуясь для копирования объектов методом Object.assign()
, который появился в стандарте ES6.
В следующем примере имеется объект с сеттером, который выводит, с помощью console.log()
то, что пытаются записать в его соответствующее свойство.
const person1 = {
set name(newName) {
console.log(newName)
}
}
person1.name = 'x' // x
Попробуем скопировать этот объект, воспользовавшись методом assign()
.
const person2 = {}
Object.assign(person2, person1)
person2.name = 'x' // в консоль ничего не попадает, сеттер не скопирован
Как видно, такой подход не работает. Свойство name
, которое в исходном объекте было сеттером, теперь представлено в виде обычного свойства.
Теперь выполним копирование объекта с использованием методов Object.defineProperties()
(он появился в ES5.1) и Object.getOwnPropertyDescriptors()
.
const person3 = {}
Object.defineProperties(person3,
Object.getOwnPropertyDescriptors(person1))
person3.name = 'x' //x
Здесь в копии объекта сеттер остался.
Надо отметить, что ограничения, характерные для Object.assign()
, свойственны и для метода Object.create()
при использовании его для клонирования объектов.
Эта возможность позволяет оставлять запятую в конце списка параметров или аргументов, соответственно, при объявлении и при вызове функций.
const doSomething = (
var1,
var2,
) => {
//...
}
doSomething(
'test1',
'test2',
)
Это повышает удобство работы с системами контроля версий. А именно, речь идёт о том, что, при добавлении новых параметров в функцию, не приходится менять существующий код только ради вставки запятой.
В стандарте ES2017 появилась конструкция async/await
, которую можно считать важнейшим новшеством этой версии языка.
Асинхронные функции представляют собой комбинацию промисов и генераторов, они упрощают конструкции, для описания которых раньше требовался большой объём шаблонного кода и неудобные в работе цепочки промисов. Фактически, речь идёт о высокоуровневой абстракции над промисами.
Когда в стандарте ES2015 появились промисы, они призваны были решить существующие проблемы с асинхронным кодом, что они и сделали. Но за те два года, которые разделяют стандарты ES2015 и ES2017, стало ясно, что промисы нельзя считать окончательным решением этих проблем.
В частности, промисы были нацелены на решение проблемы «ада коллбэков», но, решив эту проблему, они сами показали себя не с лучшей стороны из-за усложнения кода, в котором они используются. Собственно говоря, конструкция async/await
решает проблему промисов и повышает удобство работы с асинхронным кодом.
Рассмотрим пример.
function doSomethingAsync() {
return new Promise((resolve) => {
setTimeout(() => resolve('I did something'), 3000)
})
}
async function doSomething() {
console.log(await doSomethingAsync())
}
console.log('Before')
doSomething()
console.log('After')
Этот код выведет в консоль следующее.
Before
After
I did something
Как видно, после вызова doSomething()
программа продолжает выполняться, после Before
в консоль тут же выводится After
, а после того, как пройдут три секунды, выводится I did something
.
При необходимости асинхронные функции могут формировать нечто вроде цепочек вызовов. Такие конструкции отличаются лучшей читабельностью, чем нечто подобное, основанное исключительно на промисах. Это можно видеть на следующем примере.
function promiseToDoSomething() {
return new Promise((resolve)=>{
setTimeout(() => resolve('I did something'), 10000)
})
}
async function watchOverSomeoneDoingSomething() {
const something = await promiseToDoSomething()
return something + ' and I watched'
}
async function watchOverSomeoneWatchingSomeoneDoingSomething() {
const something = await watchOverSomeoneDoingSomething()
return something + ' and I watched as well'
}
watchOverSomeoneWatchingSomeoneDoingSomething().then((res) => {
console.log(res) // I did something and I watched and I watched as well
})
Здесь речь идёт об объекте SharedArrayBuffer [10], который позволяет описывать разделяемые области памяти, и об объекте Atomics [11], который содержит набор атомарных операций в виде статических методов. Подробности о возможностях, которые дают программисту эти объекты, можно почитать здесь [12].
ES9 (ES2018) — это самая свежая на момент публикации данного материала версия стандарта. Вот её основные возможности:
Promise.prototype.finally()
.
Мы уже говорили об операторах rest и spread, которые появились в ES6 и могут быть использованы для работы с массивами. Оба они выглядят как три точки. Оператор rest, в следующем примере деструктурирования массива, позволяет поместить его первый и второй элементы в константы first
и second
, а все остальные — в константу others
.
const numbers = [1, 2, 3, 4, 5]
const [first, second, ...others] = numbers
console.log(first) //1
console.log(second) //2
console.log(others) //[ 3, 4, 5 ]
Оператор spread
позволяет передавать массивы в функции, ожидающие обычные списки параметров.
const numbers = [1, 2, 3, 4, 5]
const sum = (a, b, c, d, e) => a + b + c + d + e
const res = sum(...numbers)
console.log(res) //15
Теперь, используя тот же подход, можно работать и с объектами. Вот пример использования оператора rest в операции деструктурирующего присваивания.
const { first, second, ...others } =
{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }
console.log(first) //1
console.log(second) //2
console.log(others) //{ third: 3, fourth: 4, fifth: 5 }
Вот оператор spread, применяемый при создании нового объекта на основе существующего. Этот пример продолжает предыдущий.
const items = { first, second, ...others }
console.log(items) //{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }
Новая конструкция for-await-of
позволяет вызывать асинхронные функции, возвращающие промисы, в циклах. Такие циклы ожидают разрешения промиса перед переходом к следующему шагу. Вот как это выглядит.
for await (const line of readLines(filePath)) {
console.log(line)
}
При этом надо отметить, что подобные циклы нужно использовать в асинхронных функциях — так же, как это делается при работе с конструкцией async/await
.
Если промис успешно разрешается — осуществляется вызов очередного метода then()
. Если что-то идёт не так — вызывается метод catch()
. Метод finally()
позволяет выполнять некий код независимо от того, что происходило до этого.
fetch('file.json')
.then(data => data.json())
.catch(error => console.error(error))
.finally(() => console.log('finished'))
В регулярных выражениях появилась возможность ретроспективной проверки строк (?<=
). Это позволяет искать в строках некие конструкции, перед которыми есть какие-то другие конструкции.
Возможность опережающих проверок, использующая конструкцию ?=
, имелась в регулярных выражениях, реализованных в JavaScript, и до стандарта ES2018. Такие проверки позволяют узнать, следует ли за неким фрагментом строки другой фрагмент.
const r = /Roger(?= Waters)/
const res1 = r.test('Roger is my dog')
const res2 = r.test('Roger is my dog and Roger Waters is a famous musician')
console.log(res1) //false
console.log(res2) //true
Конструкция ?!
выполняет обратную операцию — совпадение будет найдено только в том случае, если за заданной строкой не идёт другая строка.
const r = /Roger(?! Waters)/g
const res1 = r.test('Roger is my dog')
const res2 = r.test('Roger is my dog and Roger Waters is a famous musician')
console.log(res1) //true
console.log(res2) //false
При ретроспективной проверке, как уже было сказано, используется конструкция ?<=
.
const r = /(?<=Roger) Waters/
const res1 = r.test('Pink Waters is my dog')
const res2 = r.test('Roger is my dog and Roger Waters is a famous musician')
console.log(res1) //false
console.log(res2) //true
Операцию, обратную описанной, можно выполнить с помощью конструкции ?<!
.
const r = /(?<!Roger) Waters/
const res1 = r.test('Pink Waters is my dog')
const res2 = r.test('Roger is my dog and Roger Waters is a famous musician')
console.log(res1) //true
console.log(res2) //false
В регулярных выражениях можно использовать класс d
, соответствующий любой цифре, класс s
, соответствующий любому пробельному символу, класс w
, который соответствует любому буквенно-цифровому символу, и так далее. Возможность, о которой идёт речь, расширяет набор классов, которыми можно пользоваться в регулярных выражениях, позволяя работать с Unicode-последовательностями. Речь идёт о классе p{}
и об обратном ему классе P{}
.
В Unicode каждый символ имеет набор свойств. Эти свойства указываются в фигурных скобках группы p{}
. Так, например, свойство Script
определяет семейство языков, к которому принадлежит символ, свойство ASCII
, логическое, принимает значение true
для ASCII-символов, и так далее. Например, выясним, содержат ли некие строки исключительно ASCII-символы.
console.log(r.test('abc')) //true
console.log(r.test('ABC@')) //true
console.log(r.test('ABCЖ')) //false
Свойство ASCII_Hex_Digit
принимает значение true
только для символов, которые можно использовать для записи шестнадцатеричных чисел.
const r = /^p{ASCII_Hex_Digit}+$/u
console.log(r.test('0123456789ABCDEF')) //true
console.log(r.test('H')) //false
Существует и множество других подобных свойств, которые используются так же, как вышеописанные. Среди них — Uppercase
, Lowercase
, White_Space
, Alphabetic
, Emoji
.
Вот, например, как с помощью свойства Script
определить, какой алфавит используется в строке. Здесь мы проверяем строку на использование греческого алфавита.
const r = /^p{Script=Greek}+$/u
console.log(r.test('ελληνικά')) //true
console.log(r.test('hey')) //false
Подробности об этих свойствах можно почитать здесь [13].
Захваченным группам символов в ES2018 можно давать имена. Вот как это выглядит.
const re = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/
const result = re.exec('2015-01-02')
console.log(result)
/*
[ '2015-01-02',
'2015',
'01',
'02',
index: 0,
input: '2015-01-02',
groups: { year: '2015', month: '01', day: '02' } ]
*/
Без использования именованных групп те же данные были бы доступны лишь как элементы массива.
const re = /(d{4})-(d{2})-(d{2})/
const result = re.exec('2015-01-02')
console.log(result)
/*
[ '2015-01-02',
'2015',
'01',
'02',
index: 0,
input: '2015-01-02',
groups: undefined ]
*/
Использование флага s
приводит к тому, что символ .
(точка) будет, кроме прочих, соответствовать и символу новой строки. Без использования этого флага точка соответствует любому символу за исключением символа новой строки.
console.log(/hi.welcome/.test('hinwelcome')) // false
console.log(/hi.welcome/s.test('hinwelcome')) // true
Этим материалом мы завершаем публикацию переводов данного [14] руководства по JavaScript. Надеемся, эти публикации помогли тем, кто раньше с JavaScript не работал, сделать их первые шаги в программировании на этом языке.
Уважаемые читатели! Если вы раньше не писали на JS и осваивали этот язык по данному руководству — просим поделиться впечатлениями.
Автор: ru_vds
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/301133
Ссылки в тексте:
[1] Часть 1: первая программа, особенности языка, стандарты: https://habr.com/company/ruvds/blog/429552/
[2] Часть 2: стиль кода и структура программ: https://habr.com/company/ruvds/blog/429556/
[3] Часть 3: переменные, типы данных, выражения, объекты: https://habr.com/company/ruvds/blog/429838/
[4] Часть 4: функции: https://habr.com/company/ruvds/blog/430382/
[5] Часть 5: массивы и циклы: https://habr.com/company/ruvds/blog/430380/
[6] Часть 6: исключения, точка с запятой, шаблонные литералы: https://habr.com/company/ruvds/blog/430376/
[7] Часть 7: строгий режим, ключевое слово this, события, модули, математические вычисления: https://habr.com/company/ruvds/blog/431072/
[8] Часть 8: обзор возможностей стандарта ES6: https://habr.com/company/ruvds/blog/431074/
[9] Часть 9: обзор возможностей стандартов ES7, ES8 и ES9: https://habr.com/company/ruvds/blog/431872/
[10] SharedArrayBuffer: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
[11] Atomics: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Atomics
[12] здесь: https://habr.com/company/ruvds/blog/332194/
[13] здесь: https://github.com/tc39/proposal-regexp-unicode-property-escapes
[14] данного: https://medium.freecodecamp.org/the-complete-javascript-handbook-f26b2c71719c
[15] Источник: https://habr.com/post/431872/?utm_source=habrahabr&utm_medium=rss&utm_campaign=431872
Нажмите здесь для печати.