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

В веб разработке JavaScript - это сенсация. Это не просто JS фреймворк как NodeJS, React, Angular, Vue, у vanilla JS также есть большая фанбаза. Сейчас давайте поговорим о современном JavaScript. Почти во всех языках программирования есть циклы. В современном JS можно очень гибко перебрать какие-либо значения.

Вопрос в том, знаете ли вы какой цикл вам подходит лучше всего. Ведь есть и for, и обратный for, и for...of, и foreach, и for...in, и for...await. Эта статья - некая дискуссия на эту тему.

Какой цикл самый быстрый?

Ответ: обратный for

  • Когда я проводил тесты локально, я был удивлен, что обратный for является самым быстрым среди всех циклов. Ниже будет пример. Запускайте цикл по массиву с одним миллионом элементов.

  • Пожалуйста, помните, что результаты console.time() сильно зависят от системной конфигурации. Проверьте ее точность.

  • const million = 1000000;
    const arr = Array(million);
    console.time(‘⏳’);
    for (let i = arr.length; i > 0; i — ) {} // for(reverse) :- 1.5ms
    for (let i = 0; i < arr.length; i++) {} // for :- 1.6ms
    arr.forEach(v => v) // foreach :- 2.1ms
    for (const v of arr) {} // for…of :- 11.7ms
    console.timeEnd(‘⏳’);

  • У обычного for и обратного почти одинаковое время. Счетчик в обратном for считает let i = arr.length только один раз, вот откуда разница в 0,1 мс. В обычном же for после каждого прохода, ему приходится проверять условие i < arr.length. В целом, это почти не имеет значения, можно проигнорировать.

  • С другой стороны foreach - это метод прототипа массива. В сравнении с циклом for, для foreach и for…of требуется больше времени.

Какие существуют циклы и когда их использовать?

  1. Обычный и обратный for
    Наверное все знакомы с этим видом цикла. Если нам надо повторить какой-то блок кода определенное количество раз, мы может использовать for.

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

  2. forEach
    После получения элемента массива, этот метод вызывает колбэк функцию для каждого элемента. Кроме того, эта функция принимает текущий элемент и его индекс.

    foreach также позволяет использовать ключевое слово this как необязательный параметр в колбэк функции.

    const things = [‘have’, ‘fun’, ‘coding’];
    const callbackFun = (item, idex) => {
    console.log(`${item} — ${index}`);
    }
    things.foreach(callbackFun);
    o/p:- have — 0
    fun — 1
    coding — 2


    В JavaScript при использовании foreach мы не можем использовать преимущество сокращенных вычислений. Позвольте представить вам сокращенные вычисления, если вы еще с ними не знакомы. Когда мы используем логические операторы, такие как И(&&) или ИЛИ(||), мы можем пропустить итерацию цикла

  3. For..of
    Это цикл был стандартизирован в ES6. С помощью этого цикла можно итерироваться по любым итерируемым объектам, таким как массив, словарь, множество, строка и др. Вдобавок, код становится более читабельным.

    const arr = [3, 5, 7];
    const str = ‘hello’;
    for (let i of arr) {
    console.log(i); // logs 3, 5, 7
    }
    for (let i of str) {
    console.log(i); // logs ‘h’, ‘e’, ‘l’, ‘l’, ‘o’
    }


    Замечание: не следует использовать for…of в генераторах, даже если for…of заканчивается раньше. Генератор выключается после выхода из цикла, и повторная попытка его вызова больше не даст результатов.

  4. For...in
    Этот вид цикла работает с объектами, у которых есть какие-либо перечисляемые свойства. Цикл for…in возвращает имена пользовательских свойств вместе с числовыми индексами для каждого отдельного свойства.

    По этой причине лучше перебирать массивы с помощью цикла for, используя числовой индекс, из-за того, что for…in перебирает пользовательские свойства так же как и элементы массива, даже если вы изменяете объект массива (путем добавления пользовательских свойств или методов).

    const details = {firstName: ‘john’, lastName: ‘Doe’};
    let fullName = ‘’;
    for (let i in details) {
    fullName += details[i] + ‘ ‘; // fullName: john doe
    }

For...in против for...of

For...in и for...of отличаются в первую очередь итерируемыми элементами. For...in проходится по свойствам объекта, в то время как for...of проходится по значениям.

let arr= [4, 5, 6];
for (let i in arr) {
console.log(i); // ‘0’, ‘1’, ‘2’
}
for (let i of arr) {
console.log(i); // ‘4’, ‘5’, ‘6’
}

Заключение

  • Цикл for самый быстрый, но плохо читаем.

  • forEach - быстрый, а итерация подконтрольна.

  • For...of требует времени, но он более привлекателен.

  • For...in требует времени, следовательно менее удобен.

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

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

Наслаждайтесь, когда кодите.????

Комментарии (11)


  1. dopusteam
    03.04.2022 10:48
    +7

    В обычном же for после каждого прохода, ему приходится проверять условие i < arr.length

    Совсем необязательно

    Позвольте представить вам сокращенные вычисления, если вы еще с ними не знакомы. Когда мы используем логические операторы, такие как И(&&) или ИЛИ(||), мы можем пропустить итерацию цикла

    Что?

    Цикл for самый быстрый, но плохо читаем.

    А есть аргументы?

    For...in требует времени, следовательно менее удобен

    Не вижу связи

    For...of требует времени, но он более привлекателен

    Чем что?

    forEach - быстрый, а итерация подконтрольна

    Что значит итерация подконтрольна?


  1. iliazeus
    03.04.2022 11:06
    +4

    Несколько странно бенчмаркать циклы без каких-либо вычислений в теле. Есть очень большая вероятность, что компилятор соптимизировал их в no-op.


    1. kahi4
      03.04.2022 11:32
      +3

      Можно запустить в ноде с  --trace-hydrogen. Ну и несколько других способов посмотреть внутрь описаны тут Стэковерфлоу

      К слову, тогда была бы очень интересная статья, если бы автор показал во что оно превращается внутри и, как следствие, что ожидать. А то рассуждать сколько произошло вызовов чего в js можно долго, но JIT-компилятор может считать совершенно иначе.


  1. kahi4
    03.04.2022 11:24

    Правильный ответ: while.

    Ну а если серьезно, то тестировать без прогрева, десятков прогонов с усреднением и тому подобного - замечательно. По факту вы скорее измеряете где и когда хром врубает турбофан, и результат будет зависеть от чего угодно.

    Есть сайт с этим тестом с правильными прогонами и правильно написанными тестами (их комьюнити за 10 лет уже отточило), jsben.ch, но сегодня он настойчиво требует поставить какое-то приложение, в общем ведёт себя неэтично, поэтому не буду давать ссылку.

    А если по делу - то это экономия на спичках и должны быть опущены в угоду читаемости кода. Вдобавок, задачи где нужно проходить миллион записей в цикле редки и если вы заранее знаете о таком объёме, есть смысл подумать об альтернативном подходе (воркере, например, или итерациями по 10_000 элементов), покуда на вашем компьютере может успевать обсчитываться 60 раз в секунду, а на Самсунг а10 пользователя нет.


    1. vintage
      03.04.2022 12:24
      +1

      1. kahi4
        03.04.2022 12:53

        Красота!

        Спасибо за скриншот, не знаю почему он от меня хочет чего-то, может из-за мобильного браузера


  1. Dolios
    03.04.2022 11:34
    +8

    Не обижайтесь, но вам пока рановато писать статьи.


    В обычном же for после каждого прохода, ему приходится проверять условие i < arr.length.

    Лайфхак, если вас это беспокоит: for (let i = 0, l = arr.length; i < l; i++) {}
    И про бенчмаркинг почитайте, как вам правильно сказали, вы измеряете погоду на Марсе.


  1. lookreciuspin
    03.04.2022 15:29

    В начале была "затравочка" про асинхронный цикл, но в статье не увидел.

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

    С другой стороны как заметил Дональд Батькович Кнут в (Искусство Программирования): В 97 из 100 случаев мы должны забывать о небольшом повышении эффективности, ведь весь корень зла в преждевременной оптимизации. Но очень важно не упускать такую возможность в остальных 3 случаях.


  1. hellamps
    04.04.2022 06:30

    самое смешное в этом то, что если вас начала беспокоить производительность цикла в джаваскрипте - это значит его давно пора переписать с каким async/setImmediate


  1. neocity
    04.04.2022 08:32

    А как же бенчмарки, а сравнить с while и функцией-генератором (yield) ?


  1. TotalAMD
    04.04.2022 15:11
    +1

    Уважаемый, оформите свои коды с подсветкой для нормальных людей, будьте добры.