Важное примечание: эта статья больше не является точной, эти проблемы с производительностью исправлены в последних версиях V8.

JavaScript представил в ES6 оператор распространения, который помогает писать более короткий и приятный код. Я начал использовать этот оператор везде, где мог, сразу после того, как узнал о нем, потому что почему бы и нет? Это новый синтаксис, который был представлен, это будущее, он упрощает многие вещи, он делает ваш код короче, сохраняя при этом удобочитаемость, он идеален.

Недавно я начал работать над новым проектом под названием Клио. Это новый и современный язык программирования, созданный для облака. Он функциональный и компилируется в JavaScript. При написании лексера, парсера и генератора кода для этого нового языка я столкнулся с многочисленными трудностями и проблемами, вызванными производительностью и ограничениями JavaScript.

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

Давайте проанализируем некоторые из наиболее распространенных применений оператора спреда.

Функции и вызовы функций

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

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

Это действительно здорово. Это делает код короче, читабельнее и проще в обслуживании. Но каковы затраты? Я написал несколько тестов на jsperf, чтобы проверить производительность каждого метода по сравнению с другими. Вот результаты:

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

Оранжевая полоса - это когда мы определяем функцию с помощью оператора распространения и используем метод apply для передачи наших аргументов. Зеленая полоса - это когда мы определяем функцию с помощью оператора распространения и передаем аргументы с помощью оператора распространения.

Как видите, использование оператора распространения для определения функций не имеет большого значения, но при передаче аргументов оператор распространения приводит к огромному падению производительности.

Массивы

Еще одно распространенное использование оператора распространения - расширение, распаковка или объединение массивов вместе. Представьте, что у вас есть два списка чисел, и вы хотите составить список, в котором есть все элементы из этих двух списков. Конечно, вы можете использовать цикл для перебора всех элементов и добавления их в новый список, но есть также метод concat для массивов JavaScript, который мы можем использовать. И снова новый изящный способ сделать это - использовать оператор распространения:

Опять же, он выглядит красивее, короче и удобнее в обслуживании. Это чище и проще по сравнению со старым методом. Но как насчет производительности? Вот тест jsperf для измерения производительности нескольких различных способов сделать это:

Синяя полоса показывает результаты использования метода concat. Красная полоса для использования двух циклов for для перебора двух списков и перемещения их в другой. Наконец, оранжевый использует оператор спреда.

Заключение

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

В заключение, я отказался от использования оператора спреда в системе исключений и трассировки Clio.