Меняем подход к циклам

Вопрос на собеседовании — назовите все способы пройтись по всем элементам массива/объекта. Полный ответ включает в себя список:

  • циклы for, while, do while
  • for-in и for-of
  • методы map, foreach, filter, reduce, reduceRight, every, some
  • итераторы

Итераторы

Вот про итераторы часто забывают. В работе могут быть случаи, когда работать с итераторами будет комфортнее и удобнее, чем с классическим подходом. Я покажу на простом примере скрипта с разбором строки аргументов. Допустим, мы пишем CLI версию какой-то утилиты под Node.js

Наша утилита будет вызываться как-то так:

node mytool.js some arguments data

Как получить аргументы? Верно, использовать встроенное поле argv в объекте process:

console.log(process.argv)

Если запустим наш пример, получим:

node iterator.js some arguments data
[ '/usr/local/bin/node',
'/Users/mayorov/workspace/vacancy.new.hr/iterator.js',
'some',
'arguments',
'data' ]

Вы хотите обрабатывать эти аргументы. Что советуют гугл и документация? Если не вдаваться в подробности как заиспользовать какой-то очередной NPM пакет(а вы знаете, наверное, что я очень не люблю использовать чужие пакеты, которые написаны в пару строк кода и решают простые вещи. Я такие компоненты называю leftpad-like или лефтаподободобные). Для того, чтобы получить нужные данные и отсечь первые 2 элемента мы можем поступить так:

process.argv.forEach((val, index, arr) => {
// что-то делаем и сохраняем наши данные куда-то
});

Примитивный и первое что приходит в голову. Сюда же можно отнести все способы с map и reduce. Можем изменить сам массив argv:

process.argv.shift();
process.argv.shift();
// process.argv - теперь содержит только наши аргументы без "лишних" системных

Но это же не тру, нарушает иммутабельность и может повлечь к непредсказуемым результатам (вдруг в какой-то версии ноды это поле сделают readonly).

Можем создать клон массива аргументов с фильтрацией:

const argv = process.argv.filter((x, i) => i > 1);
console.log(argv);
// [ 'some', 'arguments', 'data' ]

Нууу. Работает, иммутабельно и даже можно сказать, что решение-то ок. Но минус подхода — это копия с данными. Зачем копировать?

Я покажу еще один способ с применением итератора:

const argv = process.argv[Symbol.iterator]();
const bin = argv.next().value;
const file = argv.next().value;
// У нас сохранилась позиция на 2м элементе и цикл for-of продолжит итерироваться с 2 индекса
for (let item of argv) {
console.log(item);
// Что-то тут делаем, что нам нужно
}
// Output:
some
arguments
data

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

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

const [bin, file, …argv] = process.argv;

Данный пример выглядит проще, но мы создаем копию массива с аргументами. На данном примере с разбором аргументов преимущества применения итератора не очевидны, но и задача была показать как это можно применять. Больше знаний — больше возможностей.

Что лучше — решать вам, но лучше знать про все способы, чтобы всегда был выбор 😉


Кстати, следить за обновлениями и прочими материалами от меня можно в телеграм канале: @prowebit

В этом канале публикую не только статьи из этого блога, но и различные новости и мысли. Подписывайтесь 😉