DevGang
Авторизоваться

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

Если вам нужен быстрый и малозатратный способ манипулировать большими списками в JavaScript, то итераторы и генераторы вам помогут. О том как работать с большими массивами данных в JavaScript в этой статье

Предисловие

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

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

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

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

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

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

Итераторы

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

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

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

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

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

const alpha = ['a','b','c'];
const it = alpha[Symbol.iterator]();
 
it.next();  //{ value: 'a', done: false }
it.next();  //{ value: 'b', done: false }
it.next();  //{ value: 'c', done: false }
it.next();  //{ value: undefined, done: true }

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

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

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

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

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

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);

Генераторы

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

function *genFunc() {
    ...
    yield value;
}

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

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

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. Это переработанный код с использованием функции генератора:

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();

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

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;    //0
it.next().value;    //1
it.next().value;    //1
it.next().value;    //2

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

Как итог

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

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

Источник

#JavaScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

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

Попробовать

Оплатив хостинг 25$ в подарок вы получите 100$ на счет

Получить