Welchen Nutzen hat die Javascript-Methode forEach (die Map kann das nicht)?

90

Der einzige Unterschied, den ich in map und foreach sehe, besteht darin, dass mapein Array zurückgegeben wird und forEachnicht. Ich verstehe jedoch nicht einmal die letzte Zeile der forEachMethode " func.call(scope, this[i], i, this);". Zum Beispiel ist nicht „ this“ und „ scope“ bezieht sich auf denselben Gegenstand und ist nicht this[i]und unter iBezugnahme auf den aktuellen Wert in der Schleife?

In einem anderen Beitrag ist mir aufgefallen, dass jemand sagte: "Verwenden forEachSie diese Option, wenn Sie auf der Grundlage der einzelnen Elemente der Liste etwas tun möchten. Möglicherweise fügen Sie der Seite beispielsweise Dinge hinzu. Im Wesentlichen ist dies ideal, wenn Sie" Nebenwirkungen "wünschen. Ich weiß nicht, was unter Nebenwirkungen zu verstehen ist.

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

Gibt es schließlich echte Verwendungsmöglichkeiten für diese Methoden in Javascript (da wir keine Datenbank aktualisieren), außer um Zahlen wie diese zu manipulieren:

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

Vielen Dank für jede Antwort.

John Merlino
quelle
Wie kann man die Definition einer Funktion von nativem JavaScript finden, wie Sie es mit mapund getan haben forEach? Alles, was ich von Google bekomme, sind Nutzungsspezifikationen und Tutorials.
WoodrowShigeru

Antworten:

94

Der wesentliche Unterschied zwischen mapund forEachin Ihrem Beispiel besteht darin, dass forEachdie ursprünglichen Array-Elemente verarbeitet werden, während mapals Ergebnis explizit ein neues Array zurückgegeben wird.

Mit forEachergreifen Sie eine Aktion mit - und ändern Sie optional - jedes Element im ursprünglichen Array. Die forEachMethode führt die von Ihnen für jedes Element bereitgestellte Funktion aus, gibt jedoch nichts zurück ( undefined). Geht andererseits mapdurch das Array, wendet eine Funktion auf jedes Element an und gibt das Ergebnis als neues Array aus .

Der "Nebeneffekt" forEachist, dass das ursprüngliche Array geändert wird. "Keine Nebenwirkung" mapbedeutet, dass bei der idiomatischen Verwendung die ursprünglichen Array-Elemente nicht geändert werden. Das neue Array ist eine Eins-zu-Eins-Zuordnung jedes Elements im ursprünglichen Array. Die Zuordnungstransformation ist Ihre bereitgestellte Funktion.

Die Tatsache, dass keine Datenbank beteiligt ist, bedeutet nicht, dass Sie nicht mit Datenstrukturen arbeiten müssen, was schließlich eine der Essenzen der Programmierung in jeder Sprache ist. Was Ihre letzte Frage betrifft, kann Ihr Array nicht nur Zahlen, sondern auch Objekte, Zeichenfolgen, Funktionen usw. enthalten.

Ken Redler
quelle
4
Anmerkung aus der Zukunft: Diese Antwort ist eigentlich Unsinn; .mapkann alles, was .forEachkann (einschließlich des Änderns von Elementen), gibt nur eine neue Liste zurück, die aus der Iteratorfunktion erstellt wurde.
Travis Webb
6
Antwort aus der Vergangenheit: Ich bin respektvoll anderer Meinung. .map()Erstellt ein neues Array und ändert das Original nicht. Sie könnten zwar das Array ändern, das selbst zugeordnet wird, aber das wäre zumindest nicht idiomatisch, wenn nicht unsinnig. .forEach()Obwohl ähnlich, wendet es seine Funktion auf jedes Element an, gibt aber immer zurück undefined. Ungeachtet all dessen fragt das OP auch nach seinen eigenen spezifischen Funktionen, die dem Array-Prototyp hinzugefügt wurden. In der Vergangenheit gab es hier kein ES5.
Ken Redler
4
Es gibt immer noch nichts, mit forEachdem Sie nichts anfangen können map. Sie könnten sich einfach nicht um den Rückgabewert von kümmern mapund Sie hätten einen forEach. Der Unterschied besteht darin, dass die Verwendung mapfür Aufgaben, bei denen Sie kein neues Array basierend auf den Ergebnissen erstellen möchten, sehr ineffektiv ist . Also für diejenigen, die Sie verwenden forEach.
Poke
2
@poke: ..und ebenso kannst du mit einer einfachen alten forSchleife alles machen, was du brauchst . Ich bin nicht anderer Meinung, dass es einen Unterschied in der "Effektivität" gibt, aber ich glaube auch nicht, dass irgendjemand argumentiert, dass einer etwas Magie hat, den der andere irgendwie nicht erreichen kann. Obwohl es tatsächlich eine Sache gibt, mapdie nicht möglich ist: kein Array zurückzugeben. Es gibt Wert JS aus anderen Sprachen leben bis zu Erwartungen in, wie sie sich auf das Verhalten der funktionellen Favoriten wie map, reduce, filterusw. Zum Beispiel, Sie implementieren könnten reduceRightÄhnliche Bausteine verwenden, aber warum nicht einfach verwenden reduceRight?
Ken Redler
1
@ChinotoVokro, Ihr Beispiel mutiert das ursprüngliche Array explizit, indem es b.val = b.val**2 innerhalb des zurückgegebenen Objekts zugewiesen wird . Das ist genau das, was wir sagen, ist zwar möglich, aber verwirrend und nicht idiomatisch. Normalerweise geben Sie einfach den neuen Wert zurück und weisen ihn nicht auch dem ursprünglichen Array zu:a=[{val:1},{val:2},{val:3},{val:4}]; a.map((b)=>{b.val**2}); JSON.stringify(a);
Ken Redler
66

Der Hauptunterschied zwischen den beiden Methoden ist konzeptionell und stilistisch: Sie verwenden, forEachwenn Sie etwas mit oder mit jedem Element eines Arrays tun möchten ("mit" ist das, was der von Ihnen zitierte Beitrag mit "Nebenwirkungen" meint, denke ich). , während Sie verwenden, mapwenn Sie jedes Element eines Arrays kopieren und transformieren möchten (ohne das Original zu ändern).

Da beide mapund forEacheine Funktion für jedes Element in einem Array aufrufen und diese Funktion benutzerdefiniert ist, können Sie fast nichts mit dem einen und nicht mit dem anderen tun. Es ist zwar hässlich, mapein Array an Ort und Stelle zu ändern und / oder etwas mit Array-Elementen zu tun:

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 }]

aber viel sauberer und offensichtlicher in Bezug auf Ihre Absicht zu verwenden forEach:

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

Insbesondere wenn es sich, wie es in der realen Welt normalerweise der Fall elist , um eine nützliche, für Menschen lesbare Variable handelt:

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

Auf die gleiche Weise können Sie ganz einfach forEachein neues Array erstellen:

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

aber es ist sauberer zu benutzen map:

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

Beachten Sie auch, dass mapein neues Array wahrscheinlich zumindest einige Leistungs- / Speichereinbußen erleidet, wenn Sie lediglich eine Iteration benötigen, insbesondere für große Arrays - siehe http://jsperf.com/map-foreach

Warum Sie diese Funktionen verwenden möchten, ist immer dann hilfreich, wenn Sie eine Array-Manipulation in Javascript durchführen müssen, was (selbst wenn es sich nur um Javascript in einer Browser-Umgebung handelt) ziemlich oft und fast immer vorkommt Sie greifen auf ein Array zu, das Sie nicht manuell in Ihren Code schreiben. Möglicherweise handelt es sich um ein Array von DOM-Elementen auf der Seite oder um Daten, die aus einer AJAX-Anforderung abgerufen wurden, oder um Daten, die vom Benutzer in ein Formular eingegeben wurden. Ein häufiges Beispiel ist das Abrufen von Daten aus einer externen API, mit der Sie mapdie Daten möglicherweise in das gewünschte Format umwandeln und dann forEachüber Ihr neues Array iterieren möchten , um sie Ihrem Benutzer anzuzeigen.

nrabinowitz
quelle
Ich sehe Leute, die .map()ständig Inline-Elemente ändern. Ich hatte den Eindruck , dass der Hauptvorteil der Verwendung war .mapüber.forEach
Chovy
1
@chovy Ich glaube, dass Sie basierend darauf entscheiden sollten, ob Sie eine Reihe von Ergebnissen erzielen möchten. .mapkann Elemente ändern, ja, aber es würde ein Array erstellen, das Sie nicht benötigen. Sie sollten auch überlegen, wie die Wahl des einen oder anderen den Leser erraten lässt, ob Sie Nebenwirkungen haben oder nicht
leewz
16

Die gewählte Antwort (von Ken Redler) ist irreführend.

Ein Nebeneffekt in der Informatik bedeutet, dass eine Eigenschaft einer Funktion / Methode einen globalen Zustand verändert [Wiki] . In einem engen Sinne kann dies auch das Lesen aus einem globalen Zustand und nicht aus Argumenten umfassen. Bei der Imperativ- oder OO-Programmierung treten die meisten Nebenwirkungen auf. Und Sie nutzen es wahrscheinlich, ohne es zu merken.

Der wesentliche Unterschied zwischen forEachund mapbesteht darin, dass mapSpeicher zugewiesen und der Rückgabewert gespeichert wird, während er forEachweggeworfen wird. Weitere Informationen finden Sie in der emca-Spezifikation .

Der Grund, warum Leute sagen, forEachwird verwendet, wenn Sie einen Nebeneffekt wollen, ist, dass der Rückgabewert von forEachimmer ist undefined. Wenn es keine Nebenwirkungen hat (den globalen Status nicht ändert), verschwendet die Funktion nur CPU-Zeit. Ein optimierender Compiler entfernt diesen Codeblock und ersetzt ihn durch den Endwert ( undefined).

Übrigens sollte beachtet werden, dass JavaScript keine Einschränkungen für Nebenwirkungen hat. Sie können das ursprüngliche Array weiterhin ändern 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
quelle
1
Diese Frage und ihre Antworten und Kommentare machen immer wieder Spaß, wenn Sie alle paar Jahre darauf zurückkommen. Die Art und Weise, wie Map in JS implementiert wird, ist in Bezug auf die Speicherzuweisung ein weiterer schöner Winkel.
Ken Redler
6

Dies ist eine schöne Frage mit einer unerwarteten Antwort.

Das Folgende basiert auf der offiziellen Beschreibung vonArray.prototype.map() .

Es gibt nichts , forEach()was das map()nicht kann. Das heißt, map()ist eine strenge Supermenge von forEach().

Obwohl map()normalerweise zum Erstellen eines neuen Arrays verwendet wird, kann es auch zum Ändern des aktuellen Arrays verwendet werden. Das folgende Beispiel veranschaulicht dies:

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!!

Im obigen Beispiel awurde zweckmäßigerweise so eingestellt, dass a[i] === iz i < a.length. Trotzdem zeigt es die Leistungsfähigkeit map()und insbesondere die Fähigkeit, das Array, auf dem es aufgerufen wird, zu ändern.

Anmerkung 1:
Die offizielle Beschreibung impliziert, dass map()sich die Länge des Arrays, auf dem es aufgerufen wird , sogar ändern kann ! Ich kann jedoch keinen (guten) Grund dafür sehen.

Hinweis 2:
Während map()Map eine Super-Menge von ist forEach(), forEach()sollte es immer noch verwendet werden, wenn man die Änderung eines bestimmten Arrays wünscht. Dies macht Ihre Absichten klar.

Hoffe das hat geholfen.

Sumukh Barve
quelle
8
Tatsächlich gibt es eine Sache, die forEach tun kann, die Map nicht kann - kein Array zurückgeben.
Antti Haapala
1
Sie können auch das dritte Argument für die Zuordnungsfunktion verwenden, um das Zielarray anstelle der Gültigkeitsbereichsvariablen zu mutieren:mapped = a.map(function (x, i, arr) { arr[i] = x * x * x; return x * x; });.
pdoherty926
@ pdoherty926 wahr, aber auch kann a.forEach(function(x, i, arr) { ..., was wiederum eine konzeptionell korrektere Methode ist, wenn Sie beabsichtigen, jedes Originalelement zu mutieren , anstatt Werte von einem Array auf ein anderes abzubilden
Conny
@conny: Einverstanden. Bitte beachten Sie auch diese Antwort auf eine ähnliche Frage.
Sumukh Barve
3

Sie können verwenden, mapals ob es wäre forEach.

Es wird jedoch mehr tun als nötig.

scopekann ein beliebiges Objekt sein; es ist keineswegs notwendig this.

Ob es echte Verwendungen für mapund gibt und forEachob es echte Verwendungen für foroder whileSchleifen gibt.

Wombleton
quelle
Aber warum werden hier sowohl "scope" als auch "this" genannt: func.call (scope, this [i], i, this); Ist scope nicht ein Parameter, der dem aktuellen Objekt entspricht, nämlich "this"?
John Merlino
Nein, es kann gleich dem aktuellen Objekt sein. Das Objekt selbst wird als dritter Parameter an das Array übergeben. scope = scope || thisbedeutet "wenn scopefalsch ist (undefiniert, null, falsch usw.), setzen Sie den Bereich thisstattdessen auf und fahren Sie fort".
Wombleton
Können Sie mich mit einem Beispiel verknüpfen, wenn es nicht gleichbedeutend ist?
John Merlino
developer.mozilla.org/en/Core_JavaScript_1.5_Reference/… hat eine unter "Drucken des Inhalts eines Arrays mit einer Objektmethode"
wombleton
1

Während alle vorherigen Fragen richtig sind, würde ich definitiv eine andere Unterscheidung treffen. Die Verwendung von mapund forEachkann Absicht implizieren.

Ich verwende es gerne, mapwenn ich einfach die vorhandenen Daten auf irgendeine Weise transformiere (aber sicherstellen möchte, dass die Originaldaten unverändert bleiben.

Ich verwende es gerne, forEachwenn ich die vorhandene Sammlung ändere.

Zum Beispiel,

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 }]

Meine Faustregel sicher zu sein , machen , wenn Sie mapSie sind immer etwas neues Objekt / Wert zu schaffen für jedes Element der Quellliste zurückzukehren und Rückkehr es nicht nur auf jedem Element eine Operation durchgeführt wird .

Sofern Sie die vorhandene Liste nicht wirklich ändern müssen, ist es nicht wirklich sinnvoll, sie an Ort und Stelle zu ändern, und sie passt besser in funktionale / unveränderliche Programmierstile.

maschwenk
quelle
0

Ich fühle mich wieder wie ein Nekromant, der eine Antwort auf eine Frage hinzufügt, die vor 5 Jahren gestellt wurde (zum Glück gibt es ziemlich neue Aktivitäten, also bin ich nicht der einzige, der die Toten stört :).

Andere haben bereits über Ihre Hauptfrage bezüglich des Unterschieds zwischen den Funktionen geschrieben. Aber für...

Gibt es eine echte Verwendung für diese Methoden in Javascript (da wir keine Datenbank aktualisieren), außer um Zahlen wie diese zu manipulieren:

... es ist lustig, dass du fragen solltest. Erst heute habe ich einen Code geschrieben, der eine Reihe von Werten aus einem regulären Ausdruck mehreren Variablen zuweist, wobei map zur Transformation verwendet wird. Es wurde verwendet, um eine sehr komplizierte textbasierte Struktur in visualisierbare Daten umzuwandeln ... aber der Einfachheit halber werde ich ein Beispiel mit Datumszeichenfolgen anbieten, da diese wahrscheinlich für alle vertrauter sind (obwohl mein Problem tatsächlich mit Datumsangaben bestand Anstelle einer Karte hätte ich ein Date-Objekt verwendet, das die Arbeit von sich aus hervorragend erledigt hätte.

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

Also ... während dies nur ein Beispiel war - und nur eine sehr grundlegende Transformation für die Daten durchgeführt hat (nur zum Beispiel) ... wäre dies ohne Karte eine viel mühsamere Aufgabe gewesen.

Zugegeben, wird es in einer Version von JavaScript geschrieben , dass ich nicht glaube , zu viele Browser unterstützen noch (zumindest voll) , aber - wir sind immer da. Wenn ich es im Browser ausführen müsste, würde es meiner Meinung nach gut transpilieren.

Markku Uttula
quelle
0

TL; DR Antwort -

map gibt immer ein anderes Array zurück.

forEach nicht. Es liegt an Ihnen zu entscheiden, was es tut. Geben Sie ein Array zurück, wenn Sie möchten, oder tun Sie etwas anderes, wenn Sie dies nicht tun.

Flexibilität ist in bestimmten Situationen wünschenswert. Wenn es nicht das ist, womit Sie es zu tun haben, verwenden Sie die Karte.

charsi
quelle