JavaScript - нюансы myArray.forEach и цикла for

88

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

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

вместо того:

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

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


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

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

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

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

  • Есть ли какие-либо различия между стандартом цикла for (первый пример выше) и реализацией Array.prototype.forEach в современных браузерах?
  • Есть ли разница между реализацией современных браузеров и реализацией Mozilla, указанной выше (особенно с IE8)?
  • Производительность - это не такая большая проблема, просто согласованность того, какие свойства повторяются.
Майкл Льюис
источник
6
Невозможно breakвыйти из forEach. Но большим преимуществом является создание новой области с функцией. С полифиллом проблем возникнуть не должно (по крайней мере, я не сталкивался с ними).
hgoebl
Проблемы , вы можете иметь с более старыми IE, не сама прокладка, но сломанный массив конструктор / буквальным WRT , holesгде undefinedдолжны быть и другие сломанные методы, как sliceи hasOwnPropertyWRT для arraylike объектов DOM. Мое тестирование и es5 shimпоказало, что такие методы прокладки соответствуют спецификации (прокладка MDN не проверена).
Xotic750
1
И что касается выхода из forпетли, это то some, для чего.
Xotic750
1
«Однако я не могу найти ничего, что бы предпочло объектно-ориентированный цикл:» Я бы предпочел назвать это функциональным способом, а не императивным.
Memke
Вы можете использовать Array.find()для выхода из цикла после обнаружения первого совпадения.
Mottie

Ответы:

122

Наиболее существенное различие между 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, я бы рекомендовал его использовать.


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

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

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

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

Алексис Кинг
источник
1
Стоит отметить, что версии библиотеки eachведут себя не так, как спецификация ECMA5 forEach, они склонны рассматривать все массивы как плотные (чтобы избежать ошибок IE, если вы об этом осведомлены). Иначе может быть "попался". В качестве ссылки github.com/es-shims/es5-shim/issues/190
Xotic750
7
Также есть несколько методов-прототипов, которые позволяют вам прервать работу, например, Array.prototype.someкоторые будут зацикливаться, пока вы не вернете истинное значение или пока оно полностью не пройдет через массив. Array.prototype.everyаналогичен, Array.prototype.someно останавливается, если вы возвращаете ложное значение.
axelduch
Если вы хотите увидеть результаты тестирования в разных браузерах и таких прокладках, вы можете посмотреть здесь, ci.testling.com/Xotic750/util-x
Xotic750,
Кроме того, использование Array.prototype.forEach поставит ваш код в зависимость от расширения. Например, если вы пишете код для встраивания в страницу, есть вероятность, что Array.prototype.forEach будет перезаписан чем-то другим.
Marble Daemon
2
@MarbleDaemon: Вероятность этого настолько мала, что это практически невозможно и, следовательно, ничтожно мало. Перезапись Array.prototype.forEachкакой-либо несовместимой версией может привести к поломке такого количества библиотек, что отказ от нее по этой причине в любом случае не поможет.
Алексис Кинг,
12

Вы можете использовать свою собственную функцию 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

Чайка
источник
4

Многие разработчики (например, Кайл Симпсон) предлагают использовать его, .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циклах итерационного протокола . В целом, я считаю хорошей идеей следовать концепциям FP, которые сообщество JS, похоже, готово принять.

Steviejay
источник