Рассмотрим 3 главные ошибки, вызывающие спад производительности JavaScript кода, тестируем и избавляемся от «вредных привычек».
Что если ключевые функции ECMAScript, которые вы так кропотливо изучали, – не что иное, как опасная ловушка для снижения производительности JavaScript, красиво упакованная в однострочный функциональный код обратных вызовов?
Эта история началась с выходом ES5 и новых функций массивов ( forEach , reduce , map , filter и т. д.). С этими функциями возрастала функциональность языка. А написание кода становилось более слаженным и интересным. Да и результат работы выглядел проще и понятнее.
Тогда же активно развивалась и другая среда – Node.js. Она в корне пересматривала всю концепцию full-stack разработки и предлагала более плавный переход от front-end к back-end.
Поэтому Node.js с последним ECMAScript в V8 пытается выбиться в лидеры главных серверных языков программирования. А для этого он должен обладать высокой производительностью. Конечно же, нужно учитывать и множество других параметров. И да, универсального языка еще не придумали.
Так все же, JavaScript с его готовыми решениями – это польза или вред для производительности приложений?
С ростом мощности компьютеров и ускорением сети клиентская часть JavaScript становится отличным решением не только для представления/просмотра. Но можно ли положиться на JavaScript при написании высокопроизводительных приложений со сложной архитектурой?
Чтобы ответить на этот вопрос, проанализируем результаты тестирования нескольких операций (зацикливание, дублирование массива и перебор объектов). Все тесты проводились на macOS в Node.js v10.11.0 и браузере Chrome.
1. Зацикливание массива
Первое, что приходит на ум, – это найти сумму массива с 10 тыс. элементами. Пример взят из реальной практики, когда нужно извлечь огромную таблицу из базы данных и просуммировать ее значения, не создавая лишнего запроса к базе данных.
При суммировании случайных 10 тыс. элементов через операторы for , for-of , while , forEach и reduce 10 тыс. раз. получились значения ниже:
Если погуглить, как правильно суммировать массивы, то самым популярным советом будет использование оператора reduce . Однако данное решение является крайне медленными. Попытки реализовать задачу через forEach также не увенчались успехом. Даже в новейшем for-of (ES6) хромает производительность.
Оказалось, что лидерами по части производительности (в 10 раз быстрее остальных!) стали проверенные временем while и for .
Как же получилось, что новейшее и разрекламированное решение настолько сильно тормозит JavaScript? Дело тут в следующем: reduce и forEach требуют выполнения функции обратного вызова. Данная функция вызывается рекурсивно и заполняет стэк. Кроме того, торможение обусловлено и дополнительной операцией, а также проверкой исполняемого кода (см. тут).
2. Дублирование массива
Вроде бы ничего интересного в данном пункте нет. Но это основа неизменяемых функций, для которых выходные данные генерируются без изменения входных значений.
При тестировании производительности получается интересный результат: дублирование 10 тыс. массивов из 10 тыс. случайных элементов лучше проводить классическими способами. Новомодная операция spread из […arr] , Array из Array.from(arr) в ES6 и map arr.map(x=> x) из ES5 позорно проигрывали «старичкам» slice arr.slice() и concatenate [].concat(arr) .
3. Перебор объектов
Еще одна популярная тема – перебор (итерация) объектов. Он нужен при переборе элементов JSON без поиска какого-либо конкретного значения ключа. Здесь тоже есть свои почетные ветераны – for-in for(let key in obj) или Object.keys(obj) (в ES6). Стоит вспомнить и Object.entries(obj) (в ES8), возвращающий ключи и значения.
Результаты тестирования 10 тыс. итераций объектов из 1 тыс. случайных ключей и значений в каждом:
В первых двух вариантах вместо прямого перебора объекта без ключей создается перечислимый массив значений. Последний вариант весьма сомнительный.
Заключение
Если для приложения важна высокая производительность, или серверы сталкиваются с существенной нагрузкой, то забудьте про все популярные/легко читаемые/чистые решения. Они приводят к спаду производительности JavaScript кода в 10 раз!
Прекращайте бездумно следовать популярным трендам. Подбирайте решения в соответствии с собственными целями. Для небольших приложений идеально подходит легкий и читабельный код. Для высоконагруженных серверов и приложений с объемной клиентской частью выбирайте что-то другое.