Управление памятью: обычные функции JavaScript против функций-генераторов
Едва ли не каждый программист сталкивается с проблемой перебора большого количества элементов (коллекции статей, изображения, записи в базе данных и т.д.). И всё в порядке, пока наш сервер или браузер не заявляет: “Эй, да что ты творишь-то? Не слишком ли много работы ты мне даёшь?” =) Тут приходится засучить рукава и немножко покопаться в коде.
Сперва разберёмся, что такое генератор в JavaScript. Это функция, которая может вернуть значение и затем продолжить выполнение функции. В то время как обычная функция JavaScript использует оператор return
, функция-генератор использует оператор yield
. Вот вам пример (обратите внимание на звёздочку перед именем функции):
Если вызвать функцию с любым аргументом, она вернёт итератор, а не значение (как можно было ожидать).
Для получения значения вызываем метод next()
объекта-итератора.
Здесь текущий результат хранится в свойстве value
возвращаемого объекта. А свойство done
показывает, закончила ли свою работу функция-генератор.
В приведённом выше примере функции мы указали 4
в качестве аргумента для нашей функции-генератора, поэтому функция будет возвращать значения от 0 до 4 (включительно) на каждый вызов метода next()
.
Теперь сравним функцию-генератор и обычную функцию в операторе цикла for
на большом числе элементов данных.
Предположим, что нам надо выполнить перебор большого количества случайных чисел. В обычной функции JavaScript это будет выглядеть следующим образом:
Проделывая то же с функцией-генератором JavaScript, используем следующий код:
Протестируем обе функции, создав функцию main()
, которая проверит: как изменилось использование памяти после перебора элементов.
Теперь посмотрим на использование памяти с помощью простой функции, которая задействует свойство performance
объекта window
:
Вызываем метод main()
с обычной функцией и функцией-генератором и подсчитываем объем используемой памяти с каждой из них.
Сперва выполняем стандартную функцию JavaScript main(bigAmountOfItems, 2000000)
.
Затем выполняем функцию-генератор main(bigAmountOfItemsGenerator, 2000000)
.
Здесь стандартная функция JavaScript показывает увеличение памяти ~46.5 kilobytes против лишь ~0.125 kilobytes с генератором. Происходит это потому, что с функцией-генератором не нужно хранить в оперативной памяти все 2000000 элементов. Итератор позволяет отслеживать элемент текущей итерации и продолжать возвращать следующий до конца.
Вот то главное, что позволяет разработчикам экономить энергию и отслеживать локальные переменные или вложенные циклы с функцией-генератором. Причём внутренняя логика функции никак не сказывается на внешнем коде.
Кроме того, браузеры работают по генераторам с async / await
. Но это уже тема другой статьи 🙂 Надеюсь, данная статья была для вас полезной. Спасибо за внимание 😉
Читайте также:
- Краткое введение в функции высшего порядка в JavaScript
- Советы по разработке больших приложений JavaScript
- Условный JavaScript для экспертов
Перевод статьи Evgeny Melnikov: Use JavaScript Generator Functions to Reduce Memory Utilization