Важное примечание: эта статья больше не является точной, эти проблемы с производительностью исправлены в последних версиях V8.
JavaScript представил в ES6 оператор распространения, который помогает писать более короткий и приятный код. Я начал использовать этот оператор везде, где мог, сразу после того, как узнал о нем, потому что почему бы и нет? Это новый синтаксис, который был представлен, это будущее, он упрощает многие вещи, он делает ваш код короче, сохраняя при этом удобочитаемость, он идеален.
Недавно я начал работать над новым проектом под названием Клио. Это новый и современный язык программирования, созданный для облака. Он функциональный и компилируется в JavaScript. При написании лексера, парсера и генератора кода для этого нового языка я столкнулся с многочисленными трудностями и проблемами, вызванными производительностью и ограничениями JavaScript.
Clio - функциональный язык, и он должен быть быстрым, поэтому после написания каждой новой части его кода я проводил несколько проверок производительности и тестов. После кодирования исключения и системы трассировки Clio я заметил резкое падение производительности в моих тестах. Изучив проблему, я понял, что она вызвана просто использованием оператора распространения в моем коде.
Давайте проанализируем некоторые из наиболее распространенных применений оператора спреда.
Функции и вызовы функций
Допустим, у вас есть список чисел, и вы хотите передать элементы из этого списка функции в качестве аргументов. Всегда есть старый добрый способ: определить функцию, не имеющую аргументов, использовать специальную переменную arguments и использовать apply для передачи нашего списка чисел в эту функцию.
Это выглядит немного сложным и хакерским. Альтернативный способ - это сделать с помощью ES6. Вы определяете свою функцию и используете оператор распространения, чтобы сказать, что ожидаете много аргументов, а затем снова используете оператор распространения, чтобы передать свой массив этой новой причудливой функции.
Это действительно здорово. Это делает код короче, читабельнее и проще в обслуживании. Но каковы затраты? Я написал несколько тестов на jsperf, чтобы проверить производительность каждого метода по сравнению с другими. Вот результаты:
Синяя полоса - это когда функция определяется без использования оператора распространения и вызывается без использования оператора распространения. Красная полоса - это когда мы определяем функцию без оператора спреда, но вызываем ее с помощью оператора спреда.
Оранжевая полоса - это когда мы определяем функцию с помощью оператора распространения и используем метод apply для передачи наших аргументов. Зеленая полоса - это когда мы определяем функцию с помощью оператора распространения и передаем аргументы с помощью оператора распространения.
Как видите, использование оператора распространения для определения функций не имеет большого значения, но при передаче аргументов оператор распространения приводит к огромному падению производительности.
Массивы
Еще одно распространенное использование оператора распространения - расширение, распаковка или объединение массивов вместе. Представьте, что у вас есть два списка чисел, и вы хотите составить список, в котором есть все элементы из этих двух списков. Конечно, вы можете использовать цикл для перебора всех элементов и добавления их в новый список, но есть также метод concat для массивов JavaScript, который мы можем использовать. И снова новый изящный способ сделать это - использовать оператор распространения:
Опять же, он выглядит красивее, короче и удобнее в обслуживании. Это чище и проще по сравнению со старым методом. Но как насчет производительности? Вот тест jsperf для измерения производительности нескольких различных способов сделать это:
Синяя полоса показывает результаты использования метода concat. Красная полоса для использования двух циклов for для перебора двух списков и перемещения их в другой. Наконец, оранжевый использует оператор спреда.
Заключение
Хотя оператор распространения действительно хорош и помогает сделать код более читаемым и поддерживаемым, я отказываюсь использовать его, когда это возможно. На мой взгляд, не стоит иметь кусок кода, который в 10 раз медленнее, но читается. Я, конечно, забочусь о читабельности моего кода, но есть и другие способы сохранить читаемость без снижения производительности кода.
В заключение, я отказался от использования оператора спреда в системе исключений и трассировки Clio.