Статьи

Использование итераторов и генераторов в JavaScript для оптимизации кода

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

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

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

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

К счастью, с JavaScript есть еще один вариант. Если у вас сложная вычислительная задача, вы можете использовать итераторы и генераторы для повышения эффективности. Итераторы являются свойством определенных коллекций JavaScript.

Итераторы повышают эффективность, позволяя вам использовать элементы списка по одному, как если бы они были потоком. Генераторы — это особая функция, которая может приостановить выполнение. Вызов генератора позволяет вам создавать данные по одному куску за раз без необходимости сначала сохранять их в списке.

Во-первых, давайте рассмотрим различные способы обхода коллекций в JavaScript. Цикл формы for (initial; condition; step) { ... } будет выполнять команды в своем теле определенное количество раз. Аналогично, цикл while будет выполнять команды в своем теле до тех пор, пока его условие выполняется.

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

Цикл for/in цикл for/of предназначены для итерации определенных структур данных. Итерация по структуре данных означает, что вы шагаете по каждому из ее элементов. Цикл for/in перебирает ключи в простом объекте JavaScript. Цикл for/of перебирает значения итерируемого. Что такое повторяемый? Проще говоря, итерируемый — это объект, который имеет итератор. Примерами итерируемых элементов являются массивы и множества. Итератор — это свойство объекта, обеспечивающее механизм обхода объекта.

Что делает итератор особенным, так это то, как он пересекает коллекцию Другие циклы должны загружать всю коллекцию заранее, чтобы выполнить итерацию, тогда как итератору нужно знать только текущую позицию в коллекции.

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

1
2
3
4
5
6
7
const alpha = [‘a’,’b’,’c’];
const it = alpha[Symbol.iterator]();
 
it.next();
it.next();
it.next();
it.next();

Вы также можете перебирать значения итератора, используя цикл for/of . Используйте этот метод, когда вы знаете, что хотите получить доступ ко всем элементам объекта. Вот как вы можете использовать цикл для перебора предыдущего списка:

1
2
3
for (const elem of it){
    console.log(elem);
}

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

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

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

01
02
03
04
05
06
07
08
09
10
let posts = load(url);
let it = posts[Symbol.iterator]();
 
function loadPosts(iterable, count) {
    for (let i = 0; i < count; i++) {
        display(iterable.next().value);
    }
}
 
document.getElementById(‘btnNext’).onclick = loadPosts(it, 5);

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

1
2
3
4
function *genFunc() {
    …
    yield value;
}

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

Предположим, у вас есть созданная вами онлайн-игра в пул, и вы хотите сопоставить игроков с игровыми комнатами. Ваша цель — создать все способы, которыми вы можете выбрать двух разных игроков из вашего списка из 2000 игроков. Комбинации для двух игроков, сгенерированные из списка ['a', 'b', 'c', 'd'] , будут ab, ac, ad, bc, bd, cd . Это решение с использованием вложенных циклов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function combos(list) {
    const n = list.length;
    let result = [];
   
    for (let i = 0; i < n — 1; i++) {
        for (let j = i + 1; j < n; j++) {
            result.push([list[i], list[j]]);
        }
    }
     
    return result;
}
 
console.log(combos([‘a’, ‘b’, ‘c’, ‘d’]));

Теперь попробуйте выполнить функцию со списком из 2000 элементов. (Вы можете инициализировать свой список, используя цикл for, который добавляет числа от 1 до 2000 в массив). Что происходит сейчас, когда вы запускаете свой код?

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

01
02
03
04
05
06
07
08
09
10
11
function *combos(list) {
    const n = list.length;
    for (let i = 0; i < n — 1; i++) {
        for (let j = i + 1; j < n; j++) {
            yield [list[i], list[j]];
        }
    }
}
 
let it = combos([‘a’, ‘b’, ‘c’, ‘d’]);
it.next();

Другой пример: если мы хотим сгенерировать числа в последовательности Фибоначчи до бесконечности. Вот одна реализация:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
function *fibGen() {
    let current = 0;
    let next = 1;
     
    while(true) {
        yield current;
        let nextNum = current + next;
        current = next;
        next = nextNum;
    }
}
 
let it = fibGen();
 
it.next().value;
it.next().value;
it.next().value;
it.next().value;

Обычно бесконечный цикл приведет к сбою вашей программы. Функция fibGen способна работать вечно, потому что нет условий остановки. Но так как это генератор, вы контролируете, когда выполняется каждый шаг.

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

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

Если вы ищете дополнительные ресурсы для обучения или использования в своей работе, посмотрите, что у нас есть на Envato Market .

Мы создали полное руководство, которое поможет вам изучить JavaScript , независимо от того, начинаете ли вы как веб-разработчик или хотите изучать более сложные темы.