Примеры использования некоторых новых возможностей JavaScript

Автор оригинала: Администратор all-markup-news.com
  • Перевод
Автор материала, перевод которого мы сегодня публикуем, говорит, что новые возможности JavaScript, которые попали в стандарт ES2019, уже официально доступны в браузерах Chrome, Firefox и Safari, а также на платформе Node.js. Если нужно поддерживать устаревшие браузеры, то воспользоваться новшествами можно, транспилируя JS-код с помощью Babel. Здесь мы рассмотрим примеры использования некоторых новых возможностей JS.



Метод Object.fromEntries


В ES2017 появился метод Object.entries. Он преобразует объект в массив. Например, это может выглядеть так:

let students = {
  amelia: 20,
  beatrice: 22,
  cece: 20,
  deirdre: 19,
  eloise: 21
}

Object.entries(students) 
// [
//  [ 'amelia', 20 ],
//  [ 'beatrice', 22 ],
//  [ 'cece', 20 ],
//  [ 'deirdre', 19 ],
//  [ 'eloise', 21 ]
// ]

Этот метод стал замечательным дополнением к возможностям языка. Дело в том, что он позволял удобно обрабатывать данные объектов с помощью многочисленных методов, встроенных в прототип Array. Среди этих методов, например, можно отметить map, filter, reduce. Но для того, чтобы преобразовать массив обратно в объект, к сожалению, удобных средств не существовало. Всё приходилось делать вручную, с помощью цикла:

let students = {
  amelia: 20,
  beatrice: 22,
  cece: 20,
  deirdre: 19,
  eloise: 21
}

// преобразуем объект в массив для того чтобы воспользоваться методом .filter()
let overTwentyOne = Object.entries(students).filter(([name, age]) => {
  return age >= 21
}) // [ [ 'beatrice', 22 ], [ 'eloise', 21 ] ]

// преобразуем многомерный массив обратно в объект
let drinkingAgeStudents = {}
for (let [name, age] of overTwentyOne) {
    drinkingAgeStudents[name] = age;
}
// { beatrice: 22, eloise: 21 }

Метод Object.fromEntries создан для того чтобы избавиться от подобных циклов. Он позволяет решить ту же самую задачу с помощью гораздо меньшего объёма кода. Это вполне может способствовать тому, чтобы разработчики чаще пользовались бы методами массивов для обработки преобразованных в массивы объектов.

let students = {
  amelia: 20,
  beatrice: 22,
  cece: 20,
  deirdre: 19,
  eloise: 21
}

// преобразуем объект в массив для того чтобы воспользоваться методом .filter()
let overTwentyOne = Object.entries(students).filter(([name, age]) => {
  return age >= 21
}) // [ [ 'beatrice', 22 ], [ 'eloise', 21 ] ]

// преобразуем многомерный массив обратно в объект
let drinkingAgeStudents = Object.fromEntries(overTwentyOne); 
// { beatrice: 22, eloise: 21 }

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

let students = [
  [ 'amelia', 22 ], 
  [ 'beatrice', 22 ], 
  [ 'eloise', 21], 
  [ 'beatrice', 20 ]
]

let studentObj = Object.fromEntries(students); 
// { amelia: 22, beatrice: 20, eloise: 21 }
// пропала первая запись beatrice!

▍Поддержка


  • Chrome 75
  • Firefox 67
  • Safari 12.1

Метод Array.prototype.flat


Многомерные массивы — это структуры данных, с которыми программисты встречаются довольно-таки часто. Особенно — при загрузке неких данных. При этом уменьшение размерности массива всегда было важной задачей. Решить эту задачу можно было всегда, но код её решения нельзя было назвать очень уж привлекательным.

Рассмотрим следующий пример. Здесь, в результате обработки массива объектов с помощью функции map, у нас оказывается многомерный массив. Его мы хотим сделать более «плоским».

let courses = [
  {
    subject: "math",
    numberOfStudents: 3,
    waitlistStudents: 2,
    students: ['Janet', 'Martha', 'Bob', ['Phil', 'Candace']]
  },
  {
    subject: "english",
    numberOfStudents: 2,
    students: ['Wilson', 'Taylor']
  },
  {
    subject: "history",
    numberOfStudents: 4,
    students: ['Edith', 'Jacob', 'Peter', 'Betty']
  }
]

let courseStudents = courses.map(course => course.students)
// [
//   [ 'Janet', 'Martha', 'Bob', [ 'Phil', 'Candace' ] ],
//   [ 'Wilson', 'Taylor' ],
//   [ 'Edith', 'Jacob', 'Peter', 'Betty' ]
// ]

// тут мы могли бы попытаться воспользоваться чем-то вроде [].concat.apply([], courseStudents)

Теперь в нашем распоряжении имеется метод Array.prototype.flat, который принимает необязательный аргумент, указывающий то, на какой уровень надо «поднять» элементы массива.

let courseStudents = [
  [ 'Janet', 'Martha', 'Bob', [ 'Phil', 'Candace' ] ],
  [ 'Wilson', 'Taylor' ],
  [ 'Edith', 'Jacob', 'Peter', 'Betty' ]
]

let flattenOneLevel = courseStudents.flat(1)
console.log(flattenOneLevel)
// [
//   'Janet',
//   'Martha',
//   'Bob',
//   [ 'Phil', 'Candace' ],
//   'Wilson',
//   'Taylor',
//   'Edith',
//   'Jacob',
//   'Peter',
//   'Betty'
// ]

let flattenTwoLevels = courseStudents.flat(2)
console.log(flattenTwoLevels)
// [
//   'Janet',   'Martha',
//   'Bob',     'Phil',
//   'Candace', 'Wilson',
//   'Taylor',  'Edith',
//   'Jacob',   'Peter',
//   'Betty'
// ]

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

let courseStudents = [
  [ 'Janet', 'Martha', 'Bob', [ 'Phil', 'Candace' ] ],
  [ 'Wilson', 'Taylor' ],
  [ 'Edith', 'Jacob', 'Peter', 'Betty' ]
]

let defaultFlattened = courseStudents.flat()
console.log(defaultFlattened)
// [
//   'Janet',
//   'Martha',
//   'Bob',
//   [ 'Phil', 'Candace' ],
//   'Wilson',
//   'Taylor',
//   'Edith',
//   'Jacob',
//   'Peter',
//   'Betty'
// ]

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

let courseStudents = [
  [ 'Janet', 'Martha', 'Bob', [ 'Phil', 'Candace' ] ],
  [ 'Wilson', 'Taylor' ],
  [ 'Edith', 'Jacob', 'Peter', 'Betty' ]
]

let alwaysFlattened = courseStudents.flat(Infinity)
console.log(alwaysFlattened)
// [
//   'Janet',   'Martha',
//   'Bob',     'Phil',
//   'Candace', 'Wilson',
//   'Taylor',  'Edith',
//   'Jacob',   'Peter',
//   'Betty'
// ]

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

▍Поддержка


  • Chrome 75
  • Firefox 67
  • Safari 12

Метод Array.prototype.flatMap


Вместе с методом flat в нашем распоряжении теперь оказался и новый комбинированный метод — Array.prototype.flatMap. Выше мы, на самом деле, уже видели пример ситуации, в которой этот метод может пригодиться, но давайте рассмотрим ещё один пример.

Предположим, перед нами стоит задача вставки неких элементов в массив. Как мы решили бы её раньше, до появления новых возможностей JS? Например — так:

let grades = [78, 62, 80, 64]

let curved = grades.map(grade => [grade, grade + 7])
// [ [ 78, 85 ], [ 62, 69 ], [ 80, 87 ], [ 64, 71 ] ]

let flatMapped = [].concat.apply([], curved) 
// теперь массив оказался плоским. Тут можно было бы использовать метод flat, но раньше этого метода в JS не существовало
// [
//  78, 85, 62, 69,
//  80, 87, 64, 71
// ]

Теперь, когда у нас есть метод Array.prototype.flat, этот код можно улучшить:

let grades = [78, 62, 80, 64]

let flatMapped = grades.map(grade => [grade, grade + 7]).flat()
// [
//  78, 85, 62, 69,
//  80, 87, 64, 71
// ]

Та задача, которую мы тут решаем, основана на сравнительно популярном паттерне (особенно это касается функционального программирования). Поэтому то, как красиво мы можем её решить с помощью метода flatMap, не может не радовать:

let grades = [78, 62, 80, 64]

let flatMapped = grades.flatMap(grade => [grade, grade + 7]);
// [
//  78, 85, 62, 69,
//  80, 87, 64, 71
// ]

Вспомните о том, что по умолчанию метод Array.prototype.flat работает так, будто ему передана единица. Метод flatMap ведёт себя точно так же, то есть — «поднимает» элементы массива лишь на 1 уровень. Он представляет собой результат комбинации методов map и flat.

let grades = [78, 62, 80, 64]

let flatMapped = grades.flatMap(grade => [grade, [grade + 7]]);
// [
//   78, [ 85 ],
//   62, [ 69 ],
//   80, [ 87 ],
//   64, [ 71 ]
// ]

▍Поддержка


  • Chrome 75
  • Firefox 67
  • Safari 12

Методы String.prototype.trimStart и String.prototype.trimEnd


Ещё одно приятное новшество ES2019 — это псевдонимы, которые дают некоторым строковым методам более понятные имена. Раньше в нашем распоряжении были методы String.prototype.trimRight и String.prototype.trimLeft:

let message = "   Welcome to CS 101    "
message.trimRight()
// '   Welcome to CS 101'
message.trimLeft()
// 'Welcome to CS 101   '
message.trimRight().trimLeft()
// 'Welcome to CS 101'

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

let message = "   Welcome to CS 101    "
message.trimEnd()
// '   Welcome to CS 101'
message.trimStart()
// 'Welcome to CS 101   '
message.trimEnd().trimStart()
// 'Welcome to CS 101'

▍Поддержка


  • Chrome 75
  • Firefox 67
  • Safari 12

Необязательный аргумент блока catch


Ещё одна приятная возможность ES2019 — это то, что аргумент в блоках try-catch теперь стал необязательным. Ранее всем блокам catch надо было передавать, в качестве параметра, объект исключения. Аргумент приходилось передавать catch даже в том случае, если он не использовался.

try {
  let parsed = JSON.parse(obj)
} catch(e) {
  // e можно игнорировать или использовать
  console.log("error")
}

Теперь это не так. Если объект исключения не используется в блоке catch — тогда в этот блок не нужно и ничего передавать.

try {
  let parsed = JSON.parse(obj)
} catch {
  console.log("error")
}

Это — отличная возможность, которая пригодится в тех случаях, когда программист заранее знает о том, возникновение какой нештатной ситуации приведёт к попаданию в соответствующий блок catch.

▍Поддержка


  • Chrome 75
  • Firefox 67
  • Safari 12

Изменения в методе Function.prototype.toString


Стандарт ES2019 принёс изменения в то, как работает метод функций toString. Ранее он немного искажал оформление выводимого кода:

function greeting() {
  const name = 'CSS Tricks'
  console.log(`hello from ${name}`)
}

greeting.toString()
//'function greeting() {\nconst name = \'CSS Tricks\'\nconsole.log(`hello from ${name} //`)\n}

Теперь этот метод отражает реальное представление исходного кода функций.

function greeting() {
  const name = 'CSS Tricks'
  console.log(`hello from ${name}`)
}

greeting.toString()
// 'function greeting() {\n' +
//  "  const name = 'CSS Tricks'\n" +
//  '  console.log(`hello from ${name}`)\n' +
//  '}'

▍Поддержка



Итоги


Здесь мы рассмотрели примеры использования лишь совсем немногих новых возможностей JavaScript. Если вы интересуетесь новшествами JS — загляните в этот репозиторий и в эту таблицу.

Уважаемые читатели! Сталкивались ли вы с ситуациями, в которых новые возможности JS заметно упрощают решение каких-нибудь задач?

RUVDS.com
1 098,84
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией

Похожие публикации

Комментарии 23

    –3
    Аж глаза заболели от повсеместного let
      0
      Чтобы глаза привыкли к let, надо сначала закрыть их, досчитать до десяти, и плавно открывать.
      Ну а вообще, 2019 годе, же, ну.
        +7
        2019 год же, const
          +1
          На два символа больше. Так много печатать лень =)
            +5
            Это полиция хорошего кода! Немедленно отойдите от клавиатуры в окно.
              +5

              А вы точно полиция? У вас никнейм отклеился

      +3

      Глядя на примеры с toString можно подумать, что раньше оно весь код в одну строку выводило, а теперь разбивает по строкам.


      Хотя на самом деле, раньше оно теряло выравнивание, а теперь не теряет.

        +1
        trimStart / trimEnd ведут к очевидным проблемам с rtl текстом: ведь как начало, так и конец строки находятся наоборот, и кто-то может ожидать, что trimStart на арабском тексте удалит пробелы справа.
          +2

          Так ведь оно и правда удаляет пробелы справа… Собственно, потому от названий trimLeft/trimRight и отказались.

            0
            Хм, ну я попробовал (перед тем как написать комментарий), и у меня удалились слева. Окей, тогда всё верно.
              0

              Скорее всего, пробелы удалились где положено (справа), но вы этого не поняли, потому что ваш текстовый редактор / консоль интерпретатора был в ltr режиме.

          0
          Продолжается портирование underscore в стандартную библиотеку. Логично.
            –7
            Если объект исключения не используется в блоке catch

            То это плохой код.

            Исключение либо надо как-то обрабатывать, хотя бы в тот же лог записать, либо вообще не ловить.
              +2

              Если это feature detect — то не нужно. Если исключение было при попытке записать что-то в лог — то его тоже в лог записывать не нужно.

                0
                1. А можно пример подобного feature detect? Я что-то не представляю, как по факту наличия какого-то (неизвестного!!!) исключения можно сделать вывод, о доступности/недоступности чего-либо.
                2. Как часто Вы заворачиваете console.log() в try-блок?
                  +1
                  let templateEngine;
                  
                  try {
                      eval("true");
                      templateEngine = jitTemplateEngine;
                  } catch {
                      templateEngine = interpreterTemplateEngine;
                  }
                    0

                    По-хорошему, в catch-блоке надо проверить тип ошибки, и выбросить ее дальше если он неправильный. Сэкономит потом время на отлов багов в этом месте

                      0

                      Но тут есть два вопроса:


                      1. Какой тип ошибки предполагается увидеть? В стандарте не указывается какое исключение кидает eval будучи запрещённой через CSP!


                      2. Какое ещё исключение в принципе возможно в этом блоке? От чего предполагается защищаться?


                        0

                        Хотя бы проверять, что это был EvalError, а не какой-то еще. Можно получить ReferenceError: jitTemplateEngine is not defined, если где-нибудь в имени переменной опечататься.

                          0

                          Так как вы надёжно отличите EvalError от какого-то ещё, если стандарт не указывает какоё именно исключение выбрасывать?

                            +1

                            ¯\(ツ)


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

                    +1
                    1. А можно пример подобного feature detect? Я что-то не представляю, как по факту наличия какого-то (неизвестного!!!) исключения можно сделать вывод, о доступности/недоступности чего-либо.

                    function isTouchDevice() {
                      try {
                        document.createEvent('TouchEvent');
                        return true;
                      }
                      catch {
                        return false;
                      }
                    }
                    
                0
                Не очень понятно, зачем бы на практике так часто уплощать массив, чтобы потребовались специальные методы.

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

                Самое читаемое