JavaScript - niuanse pętli myArray.forEach vs for

88

Widziałem wiele pytań, które sugerują użycie:

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

zamiast:

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

dla tablic z powodu niespójnej iteracji ( patrz tutaj ).


Jednak nie mogę znaleźć niczego, co wydaje się preferować pętlę obiektową:

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

Co wydaje mi się bardziej intuicyjne.

W przypadku mojego obecnego projektu zgodność z IE8 jest ważna i rozważam użycie polyfill Mozilli , jednak nie jestem w 100% pewien, jak to zadziała.

  • Czy są jakieś różnice między standardową pętlą for (pierwszy przykład powyżej) a implementacją Array.prototype.forEach w nowoczesnych przeglądarkach?
  • Czy jest jakaś różnica między nowoczesnymi implementacjami przeglądarek a implementacją Mozilli, o której mowa powyżej (ze szczególnym uwzględnieniem IE8)?
  • Wydajność nie jest tak dużym problemem, tylko spójność z iterowanymi właściwościami.
Michael Lewis
źródło
6
Nie można breakwyjść forEach. Ale dużą zaletą jest utworzenie nowego zakresu z funkcją. Z polyfillem nie powinieneś mieć żadnych problemów (przynajmniej ja żadnych nie spotkałem).
hgoebl
Problemy, które możesz mieć ze starszym IE, nie dotyczą samego podkładki, ale zepsutego konstruktora tablicy / literału, holesgdzie undefinedpowinien być, i innych uszkodzonych metod, takich jak slicei hasOwnPropertywrt do obiektów DOM przypominających tablice. Moje testy i es5 shimwykazały, że takie podkładki są zgodne ze specyfikacją (nie testowano podkładki MDN).
Xotic750
1
I po to, by wyrwać się z forpętli some.
Xotic750
1
„Jednak nie wydaje mi się, aby znaleźć coś, co wydaje się preferować pętlę obiektową:„ Wolałbym to nazwać funkcjonalnym sposobem, a nie imperatywem.
Memke
Możesz użyć, Array.find()aby wyrwać się z pętli po znalezieniu pierwszego dopasowania.
Mottie

Odpowiedzi:

122

Najbardziej istotna różnica między forpętlą a forEachmetodą polega na tym, że w przypadku tej pierwszej możesz breakwyjść z pętli. Możesz symulować continue, po prostu wracając z funkcji przekazanej do forEach, ale nie ma sposobu, aby całkowicie zatrzymać pętlę.

Poza tym oba te rozwiązania skutecznie realizują tę samą funkcjonalność. Inna niewielka różnica dotyczy zakresu indeksu (i wszystkich zawierających zmienne) w pętli for, ze względu na zmienne podnoszenie.

// '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) { ... });

Jednak forEachwydaje mi się, że jest to dużo bardziej wyraziste - reprezentuje zamiar iteracji przez każdy element tablicy i zapewnia odniesienie do elementu, a nie tylko do indeksu. Ogólnie rzecz biorąc, sprowadza się to głównie do osobistego gustu, ale jeśli możesz go użyć forEach, polecam go.


Pomiędzy tymi dwiema wersjami występuje kilka bardziej istotnych różnic, w szczególności dotyczących wydajności. W rzeczywistości prosta pętla for działa znacznie lepiej niż forEachmetoda, co wykazano w tym teście jsperf .

To, czy takie wykonanie jest dla Ciebie konieczne, zależy od Ciebie, aw większości przypadków wolałbym wyrazistość niż szybkość. Ta różnica szybkości jest prawdopodobnie spowodowana niewielkimi różnicami semantycznymi między podstawową pętlą a metodą podczas pracy na rzadkich tablicach, jak wspomniano w tej odpowiedzi .

Jeśli nie potrzebujesz zachowania forEachi / lub musisz wcześnie wyrwać się z pętli, możesz użyć Lo-Dash _.eachjako alternatywy, która będzie działać również w różnych przeglądarkach. Jeśli używasz jQuery, zapewnia on również podobny $.each, po prostu zwróć uwagę na różnice w argumentach przekazywanych do funkcji zwrotnej w każdej wariacji.

(Jeśli chodzi o forEachpolyfill, powinien działać w starszych przeglądarkach bez problemów, jeśli wybierzesz tę trasę.)

Alexis King
źródło
1
Warto zauważyć, że wersje bibliotek eachnie zachowują się w taki sam sposób jak specyfikacja ECMA5 forEach, mają tendencję do traktowania wszystkich tablic jako gęstych (aby uniknąć błędów IE, pod warunkiem, że jest się tego świadomym). W przeciwnym razie może to być „gotcha”. Jako odniesienie github.com/es-shims/es5-shim/issues/190
Xotic750
7
Istnieją również metody prototypowe, które pozwalają na przerwanie, takie jak Array.prototype.somezapętlenie do momentu zwrócenia prawdziwej wartości lub do całkowitego zapętlenia przez tablicę. Array.prototype.everyjest podobny do, Array.prototype.someale zatrzymuje się, jeśli zwrócisz fałszywą wartość.
axelduch
Jeśli chcesz zobaczyć wyniki testów na różnych przeglądarkach i takich podkładkach, zajrzyj tutaj, ci.testling.com/Xotic750/util-x
Xotic750
Ponadto użycie Array.prototype.forEach wystawiłoby Twój kod na łaskę rozszerzenia. Na przykład, jeśli piszesz kod, który ma być osadzony na stronie, istnieje szansa, że ​​Array.prototype.forEach zostanie nadpisany czymś innym.
Marble Daemon,
2
@MarbleDaemon Szansa na to jest tak mała, że ​​jest praktycznie niemożliwa, a zatem znikoma. Nadpisywanie Array.prototype.forEachjakąś niekompatybilną wersją mogłoby potencjalnie zepsuć tak wiele bibliotek, że unikanie tego z tego powodu i tak nie pomogłoby.
Alexis King
12

Możesz użyć swojej niestandardowej funkcji foreach, która będzie działać znacznie lepiej niż Array.forEach

Powinieneś dodać to raz do swojego kodu. To doda nową funkcję do Array.

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
});

Następnie możesz go używać w dowolnym miejscu, takim jak Array.forEach

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

});

Jedyna różnica to 3 razy szybciej. https://jsperf.com/native-arr-foreach-vs-custom-foreach

AKTUALIZACJA: W nowej wersji Chrome wydajność .forEach () została poprawiona. Jednak rozwiązanie to może zapewnić dodatkową wydajność w innych przeglądarkach.

JSPerf

Mewa
źródło
4

Wielu programistów (np. Kyle Simpson) .forEachsugeruje użycie do wskazania, że ​​tablica będzie miała efekt uboczny i .mapdla czystych funkcji. forpętle pasują dobrze jako rozwiązanie ogólnego przeznaczenia dla znanej liczby pętli lub dowolnego innego przypadku, który nie pasuje, ponieważ jest łatwiejszy w komunikacji ze względu na szerokie wsparcie w większości języków programowania.

na przykład

/* 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()));

Z punktu widzenia konwencji wydaje się to najlepsze, jednak społeczność nie zdecydowała się na konwencję dotyczącą nowszych for inpętli protokołów iteracyjnych . Ogólnie uważam, że dobrym pomysłem jest podążanie za koncepcjami FP, które społeczność JS wydaje się być otwarta na przyjęcie.

steviejay
źródło