jakie zastosowanie ma metoda javascript forEach (ta mapa nie potrafi)?

91

Jedyną różnicą, jaką widzę w map i foreach, jest to, że mapzwraca tablicę, a forEachnie jest. Jednak nie rozumiem nawet ostatniego wiersza forEachmetody „ func.call(scope, this[i], i, this);”. Na przykład, nie jest „ this” i „ scope”, odnosząc się do tego samego obiektu, a nie jest this[i], a iodnosząc się do aktualnej wartości w pętli?

Zauważyłem w innym poście, że ktoś powiedział: „Użyj, forEachgdy chcesz coś zrobić na podstawie każdego elementu listy. Możesz na przykład dodawać rzeczy do strony. Zasadniczo jest świetny, gdy chcesz mieć„ efekty uboczne ”. Nie wiem, co oznaczają skutki uboczne.

Array.prototype.map = function(fnc) {
    var a = new Array(this.length);
    for (var i = 0; i < this.length; i++) {
        a[i] = fnc(this[i]);
    }
    return a;
}

Array.prototype.forEach = function(func, scope) { 
    scope = scope || this; 
    for (var i = 0, l = this.length; i < l; i++) {
        func.call(scope, this[i], i, this); 
    } 
}

Wreszcie, czy istnieją jakieś rzeczywiste zastosowania tych metod w javascript (ponieważ nie aktualizujemy bazy danych) inne niż manipulowanie liczbami w następujący sposób:

alert([1,2,3,4].map(function(x){ return x + 1})); //this is the only example I ever see of map in javascript.

Dzięki za każdą odpowiedź.

JohnMerlino
źródło
Jak można znaleźć definicję funkcji natywnego JavaScript, tak jak w przypadku mapi forEach? Wszystko, co otrzymuję od Google, to specyfikacje użytkowania i samouczki.
WoodrowShigeru
Zobacz także język obojętny Czy istnieje różnica między foreach a mapą?
Bergi

Odpowiedzi:

95

Zasadnicza różnica między mapiw forEachtwoim przykładzie polega na tym, że forEachoperuje na oryginalnych elementach tablicy, podczas gdy mapjawnie zwraca jako wynik nową tablicę.

Z tym, forEachże wykonujesz jakąś akcję z - i opcjonalnie zmieniasz - każdy element w oryginalnej tablicy. forEachMetoda uruchamia funkcję dostarczania dla każdego elementu, ale nic nie zwraca ( undefined). Z drugiej strony mapprzechodzi przez tablicę, stosuje funkcję do każdego elementu i emituje wynik jako nową tablicę .

„Efekt uboczny” forEachpolega na tym, że oryginalna tablica jest zmieniana. „Brak efektu ubocznego” mapoznacza, że ​​w użyciu idiomatycznym oryginalne elementy tablicy nie są zmieniane; nowa tablica to mapowanie jeden do jednego każdego elementu w oryginalnej tablicy - transformacja odwzorowania jest dostarczoną funkcją.

Fakt, że nie ma żadnej bazy danych, nie oznacza, że ​​nie będziesz musiał operować na strukturach danych, co jest przecież jedną z esencji programowania w jakimkolwiek języku. Jeśli chodzi o twoje ostatnie pytanie, twoja tablica może zawierać nie tylko liczby, ale także obiekty, ciągi znaków, funkcje itp.

Ken Redler
źródło
4
uwaga z przyszłości: ta odpowiedź to właściwie nonsens; .mapmoże zrobić wszystko, co .forEachmoże (w tym modyfikować elementy), po prostu zwraca nową listę utworzoną z funkcji iteratora.
Travis Webb
6
Odpowiadając z przeszłości: z całym szacunkiem się nie zgadzam. .map()tworzy nową tablicę i nie zmienia oryginału. Rzeczywiście można zmodyfikować tablicę, która sama jest mapowana, ale byłoby to co najmniej nie idiomatyczne, jeśli nie bezsensowne. .forEach(), chociaż podobnie, stosuje swoją funkcję do każdego elementu, ale zawsze zwraca undefined. Niezależnie od powyższego, OP pyta również o własne specyficzne funkcje dodane do prototypu tablicy; w przeszłości nie było tu ES5.
Ken Redler
4
Nadal nie forEachma nic takiego , czego nie możesz zrobić map. Możesz po prostu nie przejmować się wartością zwracaną z mapi miałbyś rozszerzenie forEach. Różnica polega na tym, że jest bardzo nieefektywne w mapprzypadku zadań, w których nie chcesz tworzyć nowej tablicy na podstawie wyników. Więc dla nich używasz forEach.
szturchnij
2
@poke: ... i podobnie, możesz zrobić wszystko, czego potrzebujesz, używając zwykłej starej forpętli. Nie zaprzeczam, że jest jakaś różnica w „skuteczności”, ale też nie sądzę, żeby ktokolwiek twierdził, że jeden ma jakąś magię, której druga nie może jakoś osiągnąć. Chociaż w rzeczywistości jest jedna rzecz, mapktórej nie można zrobić: nie zwracać tablicy. Jest to wartość posiadaniem JS sprostać oczekiwaniom innych językach, z zachowaniem funkcjonalnych ulubionych jak map, reduce, filter, itd. Na przykład, można realizować reduceRightprzy użyciu podobnych bloków, ale dlaczego nie wystarczy użyć reduceRight?
Ken Redler
1
@ChinotoVokro, Twój przykład jawnie modyfikuje oryginalną tablicę, przypisując ją b.val = b.val**2 wewnątrz zwróconego obiektu . Dokładnie taka rzecz, o której mówimy, jest rzeczywiście możliwa, ale myląca i nie idiomatyczna. Zwykle po prostu zwracasz nową wartość, nie przypisując jej również do oryginalnej tablicy:a=[{val:1},{val:2},{val:3},{val:4}]; a.map((b)=>{b.val**2}); JSON.stringify(a);
Ken Redler,
66

Główną różnicą między tymi dwoma metodami jest koncepcyjny i stylistyczną: użyć forEach, gdy chcesz zrobić coś do lub z każdego elementu tablicy (robi „z” jest to, co po zacytowanie rozumie się przez „skutków ubocznych”, jak sądzę) , podczas mapgdy używasz, gdy chcesz skopiować i przekształcić każdy element tablicy (bez zmiany oryginału).

Ponieważ oba mapi forEachwywołują funkcję na każdym elemencie tablicy, a ta funkcja jest zdefiniowana przez użytkownika, prawie nic nie można zrobić z jednym, a nie z drugim. Możliwe jest, choć brzydkie, użycie mapdo modyfikacji tablicy w miejscu i / lub zrobienia czegoś z elementami tablicy:

var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.map(function(el) {
    el.val++; // modify element in-place
    alert(el.val); // do something with each element
});
// a now contains [{ val: 2 }, { val: 3 }, { val: 4 }]

ale dużo czystsze i bardziej oczywiste, jeśli chodzi o zamiar użycia forEach:

var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.forEach(function(el) { 
    el.val++;
    alert(el.val);
});

Zwłaszcza jeśli, jak to zwykle bywa w prawdziwym świecie, eljest to użyteczna zmienna czytelna dla człowieka:

cats.forEach(function(cat) { 
    cat.meow(); // nicer than cats[x].meow()
});

W ten sam sposób możesz łatwo użyć forEachdo utworzenia nowej tablicy:

var a = [1,2,3],
    b = [];
a.forEach(function(el) { 
    b.push(el+1); 
});
// b is now [2,3,4], a is unchanged

ale jest czystszy w użyciu map:

var a = [1,2,3],
    b = a.map(function(el) { 
        return el+1; 
    });

Zauważ również, że ponieważ maptworzy nową tablicę, prawdopodobnie powoduje to co najmniej pewne uderzenie w wydajność / pamięć, gdy wszystko, czego potrzebujesz, to iteracja, szczególnie w przypadku dużych tablic - patrz http://jsperf.com/map-foreach

Jeśli chodzi o powody, dla których chcesz używać tych funkcji, są one pomocne za każdym razem, gdy musisz wykonać manipulację tablicami w javascript, co (nawet jeśli mówimy tylko o javascript w środowisku przeglądarki) jest dość często, prawie zawsze uzyskujesz dostęp do tablicy, której nie zapisujesz ręcznie w swoim kodzie. Możesz mieć do czynienia z tablicą elementów DOM na stronie, danymi pobranymi z żądania AJAX lub danymi wprowadzonymi w formularzu przez użytkownika. Jednym z typowych przykładów, z którymi się spotykam, jest pobieranie danych z zewnętrznego interfejsu API, w którym możesz chcieć mapprzekształcić dane w żądany format, a następnie użyć forEachdo iteracji nowej tablicy w celu wyświetlenia jej użytkownikowi.

nrabinowitz
źródło
Widzę, że ludzie .map()cały czas używają do modyfikowania elementów wbudowanych. Miałem wrażenie, że to główna zaleta używania .mapover.forEach
chovy
1
@chovy Uważam, że powinieneś wybrać na podstawie tego, czy chcesz uzyskać szereg wyników. .mapmoże modyfikować elementy, tak, ale stworzyłoby tablicę, której nie potrzebujesz. Powinieneś także rozważyć, w jaki sposób wybranie jednego lub drugiego pozwala czytelnikowi odgadnąć, czy masz skutki uboczne, czy nie
leewz
16

Głosowana odpowiedź (od Kena Redlera) jest myląca.

Efekt uboczny w środkach informatyki że właściwością funkcja / metoda zmienia stan globalnej [wiki] . W pewnym wąskim sensie może to również obejmować czytanie ze stanu globalnego, a nie z argumentów. W programowaniu imperatywnym lub OO efekty uboczne pojawiają się przez większość czasu. I prawdopodobnie używasz tego, nie zdając sobie z tego sprawy.

Istotna różnica między forEachi mappolega na tym, że mapalokuje pamięć i przechowuje zwracaną wartość, a jednocześnie forEachją odrzuca. Więcej informacji można znaleźć w specyfikacji emca .

Jeśli chodzi o powód, dla którego ludzie mówią, że forEachjest używany, gdy chcesz mieć efekt uboczny, wartość zwracana forEachjest zawsze undefined. Jeśli nie ma efektu ubocznego (nie zmienia stanu globalnego), to po prostu marnuje czas procesora. Optymalizujący kompilator wyeliminuje ten blok kodu i zastąpi go ostateczną wartością ( undefined).

Nawiasem mówiąc, należy zauważyć, że JavaScript nie ma ograniczeń co do skutków ubocznych. Wciąż możesz modyfikować oryginalną tablicę wewnątrz map.

var a = [1,2,3]; //original
var b = a.map( function(x,i){a[i] = 2*x; return x+1} );
console.log("modified=%j\nnew array=%j",a,b);
// output:
// modified=[2,4,6]
// new array=[2,3,4]
Jack Tang
źródło
2
To pytanie, odpowiedzi na nie i komentarze są zawsze zabawne, gdy powracam do nich co kilka lat. Sposób implementacji mapy w JS, pod względem alokacji pamięci, to kolejny fajny punkt widzenia.
Ken Redler
7

To piękne pytanie z nieoczekiwaną odpowiedzią.

Poniższe informacje oparte są na oficjalnym opisieArray.prototype.map() .

Nie ma nic, co forEach()mogłoby to zrobić map(). Oznacza to, że map()jest ścisły super zestaw z forEach().

Chociaż map()jest zwykle używany do tworzenia nowej tablicy, może być również używany do zmiany bieżącej tablicy. Poniższy przykład ilustruje to:

var a = [0, 1, 2, 3, 4], mapped = null;
mapped = a.map(function (x) { a[x] = x*x*x; return x*x; });
console.log(mapped); // logs [0, 1, 4, 9, 16]  As expected, these are squares.
console.log(a); // logs [0, 1, 8, 27, 64] These are cubes of the original array!!

W powyższym przykładzie azostał dogodnie ustawiony taki, że a[i] === idla i < a.length. Mimo to demonstruje moc map(), aw szczególności zdolność do zmiany tablicy, w której jest wywoływana.

Uwaga 1:
Oficjalny opis sugeruje, że map()może nawet zmienić długość tablicy, na której jest wywołany! Jednak nie widzę (dobrego) powodu, aby to zrobić.

Uwaga 2:
Chociaż map()mapa jest super zestawem forEach(), forEach()powinno być nadal używane, gdy ktoś chce zmienić daną tablicę. To wyjaśnia twoje intencje.

Mam nadzieję, że to pomogło.

Sumukh Barve
źródło
8
Właściwie jest jedna rzecz, której forEach może zrobić, której mapa nie może zrobić - nie zwraca tablicy.
Antti Haapala
1
Możesz również użyć trzeciego argumentu funkcji mapowania, aby zmodyfikować tablicę docelową, zamiast zmiennej o określonym zakresie:mapped = a.map(function (x, i, arr) { arr[i] = x * x * x; return x * x; });.
pdoherty926
@ pdoherty926 prawda, ale tak samo może a.forEach(function(x, i, arr) { ..., co jest koncepcyjnie bardziej poprawnym sposobem, gdy istnieje zamiar zmutowania każdego oryginalnego elementu w przeciwieństwie do mapowania wartości z jednej tablicy do drugiej
conny
@conny: Zgoda. Zobacz również tę odpowiedź na podobne pytanie.
Sumukh Barve
3

Możesz używać maptak, jakby to było forEach.

Zrobi jednak więcej, niż musi.

scopemoże być dowolnym przedmiotem; to wcale nie jest konieczne this.

Jeśli chodzi o to, czy istnieją rzeczywiste zastosowania mapi forEach, a także zapytać, czy istnieją rzeczywiste zastosowania pętli forlub whilepętli.

wombleton
źródło
Ale dlaczego nazywane są tutaj zarówno „zakres”, jak i „to”: func.call (zakres, to [i], i, to); Czy zasięg nie jest parametrem, który jest równy bieżącemu obiektowi, którym jest „this”?
JohnMerlino,
Nie, może być równy bieżącemu obiektowi. Sam obiekt jest przekazywany jako trzeci parametr do tablicy. scope = scope || thisoznacza "jeśli scopejest fałszywy (niezdefiniowany, null, fałsz itp.) thiszamiast tego ustaw zakres na i kontynuuj ".
wombleton
Czy możesz połączyć mnie z przykładem, który nie jest równy temu?
JohnMerlino
developer.mozilla.org/en/Core_JavaScript_1.5_Reference/ ... ma jeden w sekcji „Drukowanie zawartości tablicy za pomocą metody obiektu”
wombleton
1

Chociaż wszystkie poprzednie pytania są poprawne, zdecydowanie dokonałbym innego rozróżnienia. Użycie mapi forEachmoże sugerować zamiar.

Lubię używać, mapgdy po prostu przekształcam istniejące dane w jakiś sposób (ale chcę się upewnić, że oryginalne dane są niezmienione.

Lubię używać, forEachgdy modyfikuję kolekcję w miejscu.

Na przykład,

var b = [{ val: 1 }, { val: 2 }, { val: 3 }];
var c = b.map(function(el) {
    return { val: el.val + 1 }; // modify element in-place
});
console.log(b);
//  [{ val: 1 }, { val: 2 }, { val: 3 }]
console.log(c);
//  [{ val: 3 }, { val: 4 }, { val: 5 }]

Moja praktyczna zasada to upewnienie się, mapże zawsze tworzysz nowy obiekt / wartość do zwrócenia dla każdego elementu listy źródłowej i zwracasz go, zamiast wykonywać jakąś operację na każdym elemencie.

Jeśli nie masz prawdziwej potrzeby modyfikowania istniejącej listy, modyfikowanie jej w miejscu nie ma sensu i lepiej pasuje do funkcjonalnych / niezmiennych stylów programowania.

maschwenk
źródło
1

Odpowiedź TL; DR -

map zawsze zwraca inną tablicę.

forEach tego nie robi. To do Ciebie należy decyzja, co zrobi. Jeśli chcesz, zwróć tablicę lub zrób coś innego, jeśli tego nie zrobisz.

W pewnych sytuacjach pożądana jest elastyczność. Jeśli to nie jest z tym, z czym masz do czynienia, użyj mapy.

charsi
źródło
0

Po raz kolejny czuję się jak nekromanta, dodając odpowiedź na pytanie, które padło 5 lat temu (szczęśliwie jest raczej niedawna aktywność, więc nie tylko ja przeszkadzam zmarłym :).

Inni już napisali o Twoim głównym pytaniu dotyczącym różnicy między funkcjami. Ale dla...

czy są jakieś rzeczywiste zastosowania tych metod w javascript (ponieważ nie aktualizujemy bazy danych) inne niż manipulowanie liczbami w ten sposób:

... to zabawne, że powinieneś zapytać. Właśnie dzisiaj napisałem fragment kodu, który przypisuje wiele wartości z wyrażenia regularnego do wielu zmiennych za pomocą map do transformacji. Został użyty do konwersji bardzo skomplikowanej struktury opartej na tekście na dane, które można wizualizować ... ale dla uproszczenia podam przykład z użyciem ciągów dat, ponieważ są one prawdopodobnie bardziej znane każdemu (choćby mój problem dotyczył dat zamiast mapy użyłbym obiektu Date, który sam by sobie świetnie poradził).

const DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z$/;
const TEST_STRING = '2016-01-04T03:20:00.000Z';

var [
    iYear,
    iMonth,
    iDay,
    iHour,
    iMinute,
    iSecond,
    iMillisecond
    ] = DATE_REGEXP
        // We take our regular expression and...
        .exec(TEST_STRING)
        // ...execute it against our string (resulting in an array of matches)...
        .slice(1)
        // ...drop the 0th element from those (which is the "full string match")...
        .map(value => parseInt(value, 10));
        // ...and map the rest of the values to integers...

// ...which we now have as individual variables at our perusal
console.debug('RESULT =>', iYear, iMonth, iDay, iHour, iMinute, iSecond, iMillisecond);

Więc ... chociaż to był tylko przykład - i wykonałem tylko bardzo podstawową transformację danych (tylko dla przykładu) ... zrobienie tego bez mapy byłoby znacznie bardziej żmudnym zadaniem.

To prawda, jest napisane w wersji JavaScript, którą, jak sądzę, nie obsługuje jeszcze zbyt wiele przeglądarek (przynajmniej w pełni), ale - osiągamy to. Gdybym musiał uruchomić to w przeglądarce, uważam, że ładnie by się transponował.

Markku Uttula
źródło