JavaScript - Нюанси myArray.forEach vs for циклу


88

Я бачив безліч питань, які пропонують використовувати:

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

замість:

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

для масивів через непослідовну ітерацію ( див. тут ).


Однак я, здається, не можу знайти нічого, що, здається, віддає перевагу об'єктно-орієнтованому циклу:

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

Що здається мені набагато інтуїтивнішим.

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

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

6
breakВийти неможливо forEach. Але великою перевагою є створення нової області дії з функцією. З поліфілом у вас не повинно виникнути проблем (принаймні, я не стикався з ними).
hgoebl

Проблеми, які можуть виникнути у старішого IE, це не сам шим, а непрацюючий конструктор масиву / літерал wrt, holesде undefinedмає бути, та інші непрацездатні методи, як-от sliceі hasOwnPropertywrt для масивно-подібних об'єктів DOM. У es5 shimході мого тестування ми показали такі підшивані методи, що відповідають специфікації (не перевірені прокладки MDN).
Xotic750

1
І до того, щоб вирватися з forциклу, ось для чого some.
Xotic750

1
"Однак я, здається, не можу знайти нічого, що, здається, віддає перевагу об'єктно-орієнтованому циклу:" Я б скоріше назвав це функціональним способом проти імперативу.
Мемке

Ви можете використовувати, Array.find()щоб вирватися з циклу після пошуку першого збігу.
Мотті

Відповіді:


121

Найбільш суттєвою відмінністю між 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, я б рекомендував його використовувати.


Існує ще кілька суттєвих відмінностей між двома версіями, зокрема щодо продуктивності. Насправді цикл simple for працює набагато краще, ніж forEachметод, як продемонстрував цей тест jsperf .

Чи необхідна вам така продуктивність, вирішувати вам, і в більшості випадків я віддав би перевагу виразності порівняно зі швидкістю. Ця різниця в швидкості, ймовірно, обумовлена ​​незначними семантичними відмінностями між основним циклом та методом при роботі на розріджених масивах, як зазначено у цій відповіді .

Якщо вам не потрібна поведінка forEachта / або вам потрібно вирватися з циклу достроково, ви можете використовувати Lo-Dash's _.eachяк альтернативу, яка також буде працювати в різних браузерах. Якщо ви використовуєте jQuery, він також надає подібне $.each, просто зверніть увагу на різницю в аргументах, переданих функції зворотного виклику в кожній варіації.

(Що стосується forEachpolyfill, він повинен працювати у старих браузерах без проблем, якщо ви вирішите піти цим шляхом.)


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, здається, відкрита для прийняття.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.