JavaScript - Нюансы myArray.forEach против цикла

Я видел много вопросов, которые предлагают использовать:

for (var i = 0; i < myArray.length; i++){ /* ... */ }

вместо:

for (var i in myArray){ /* ... */ }

для массивов из-за несогласованной итерации (см. здесь).


Однако я не могу найти ничего похожего на объектно-ориентированный цикл:

myArray.forEach(function(item, index){ /* ... */ });

Что мне кажется более интуитивным.

Для моего текущего проекта важна совместимость IE8, и я рассматриваю возможность использования Mozilla polyfill, однако я не на 100% как это будет работать.

  • Существуют ли различия между стандартом цикла (первый пример выше) и реализацией Array.prototype.forEach современными браузерами?
  • Есть ли разница между современными реализациями браузеров и реализацией Mozilla, связанными с выше (с особым учетом IE8)?
  • Производительность - это не такая уж большая проблема, просто согласованность с тем, какие свойства повторяются.

Ответ 1

Самое существенное различие между циклом for и методом forEach заключается в том, что с первым вы можете break выйти из цикла. Вы можете имитировать continue, просто возвращаясь из функции, переданной в forEach, но нет возможности полностью остановить цикл.

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

// 'i' is scoped to the containing function
for (var i = 0; i < arr.length; i++) { ... }

// 'i' is scoped to the internal function
arr.forEach(function (el, i) { ... });

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


Есть несколько существенных различий между этими двумя версиями, особенно в отношении производительности. Фактически, простой цикл цикла работает значительно лучше, чем метод forEach, как показано этот тест jsperf.

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

Если вам не нужно поведение forEach и/или вам нужно вырваться из цикла раньше, вы можете использовать Lo-Dash _.each в качестве альтернативы, которая также будет работать в кросс-браузере. Если вы используете jQuery, он также предоставляет аналогичный $.each, просто обратите внимание на различия в аргументах, переданных функции обратного вызова в каждом варианте.

(Что касается forEach polyfill, он должен работать в старых браузерах без проблем, если вы решите пойти по этому маршруту.)

Ответ 2

Вы можете использовать свою пользовательскую функцию foreach, которая будет работать намного лучше, чем Array.forEach.

Вы должны добавить это один раз в свой код. Это добавит новую функцию в массив.

function foreach(fn) {
    var arr = this;
    var len = arr.length;
    for(var i=0; i<len; ++i) {
        fn(arr[i], i);
    }
}

Object.defineProperty(Array.prototype, 'customForEach', {
    enumerable: false,
    value: foreach
});

Тогда вы можете использовать его в любом месте, как Array.forEach

[1,2,3].customForEach(function(val, i){

});

Единственная разница это в 3 раза быстрее. https://jsperf.com/native-arr-foreach-vs-custom-foreach

ОБНОВЛЕНИЕ: В новой версии Chrome улучшена производительность .forEach(). Однако решение может дать дополнительную производительность в других браузерах.

JSPerf

Ответ 3

Многие разработчики (например, Кайл Симпсон) предлагают использовать .forEach чтобы указать, что массив будет иметь побочный эффект и .map для чистых функций. Циклы for подходят как универсальное решение для известного числа циклов или любого другого случая, который не подходит, так как легче общаться из-за его широкой поддержки в большинстве языков программирования.

например

/* For Loop known number of iterations */
const numberOfSeasons = 4;
for (let i = 0; i < numberOfSeasons; i++) {
  //Do Something
}

/* Pure transformation */
const arrayToBeUppercased = ['www', 'html', 'js', 'us'];
const acronyms = arrayToBeUppercased.map((el) => el.toUpperCase));

/* Impure, side-effects with .forEach */
const acronymsHolder = [];
['www', 'html', 'js', 'us'].forEach((el) => acronymsHolder.push(el.toUpperCase()));

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