Quelle est l'utilité de la méthode javascript forEach (cette carte ne peut pas faire)?

90

La seule différence que je vois dans map et foreach est que maprenvoie un tableau et forEachne l'est pas. Cependant, je ne comprends même pas la dernière ligne de la forEachméthode " func.call(scope, this[i], i, this);". Par exemple, n'est pas « this» et « scope» se référant à un même objet et n'est pas this[i]et se iréférant à la valeur actuelle dans la boucle?

J'ai remarqué dans un autre message que quelqu'un a dit "À utiliser forEachlorsque vous voulez faire quelque chose sur la base de chaque élément de la liste. Vous pourriez ajouter des éléments à la page, par exemple. Essentiellement, c'est génial lorsque vous voulez des" effets secondaires ". Je ne sais pas ce que l'on entend par effets secondaires.

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

Enfin, y a-t-il de réelles utilisations de ces méthodes en javascript (puisque nous ne mettons pas à jour une base de données) autre que de manipuler des nombres comme celui-ci:

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

Merci pour toute réponse.

JohnMerlino
la source
Comment trouver la définition d'une fonction du JavaScript natif, comme vous l'avez fait avec mapet forEach? Tout ce que j'obtiens de Google, ce sont des spécifications d'utilisation et des didacticiels.
WoodrowShigeru
Voir aussi la langue indépendante Y a
Bergi

Réponses:

94

La différence essentielle entre mapet forEachdans votre exemple est que forEachfonctionne sur les éléments du tableau d'origine, alors que maprenvoie explicitement un nouveau tableau en conséquence.

Avec forEachvous prenez des mesures avec - et éventuellement en changeant - chaque élément du tableau d'origine. La forEachméthode exécute la fonction que vous fournissez pour chaque élément, mais ne renvoie rien ( undefined). D'autre part, mapparcourt le tableau, applique une fonction à chaque élément et émet le résultat sous la forme d'un nouveau tableau .

L '«effet secondaire» avec forEachest que le tableau d'origine est en cours de modification. "Aucun effet secondaire" mapsignifie que, dans un usage idiomatique, les éléments originaux du tableau ne sont pas modifiés; le nouveau tableau est un mappage un-à-un de chaque élément du tableau d'origine - la transformation de mappage étant votre fonction fournie.

Le fait qu'il n'y ait pas de base de données impliquée ne signifie pas que vous n'aurez pas à opérer sur des structures de données, qui, après tout, est l'une des essences de la programmation dans n'importe quel langage. Quant à votre dernière question, votre tableau peut contenir non seulement des nombres, mais des objets, des chaînes, des fonctions, etc.

Ken Redler
la source
4
note du futur: cette réponse est en fait un non-sens; .mappeut tout .forEachfaire (y compris la modification d'éléments), il renvoie simplement une nouvelle liste construite à partir de la fonction itérateur.
Travis Webb
6
Réagir du passé: Je ne suis respectueusement pas d'accord. .map()crée un nouveau tableau et ne modifie pas l'original. Vous pouvez en effet modifier le tableau qui est lui-même mappé, mais ce serait à tout le moins non idiomatique, voire absurde. .forEach(), bien que similaire, applique sa fonction à chaque élément, mais retourne toujours undefined. Nonobstant tout ce qui précède, l'OP pose également des questions sur ses propres fonctions spécifiques ajoutées au prototype du tableau; il n'y avait pas d'ES5 ici dans le passé.
Ken Redler
4
Il n'y a toujours rien qui forEachfasse que vous ne puissiez pas faire map. Vous ne pourriez tout simplement pas vous soucier de la valeur de retour de mapet vous auriez un forEach. La différence est qu'il est super inefficace à utiliser mappour les tâches où vous ne souhaitez pas créer un nouveau tableau en fonction des résultats. Donc pour ceux-là, vous utilisez forEach.
poke
2
@poke: .. et de même, vous pouvez faire ce dont vous avez besoin avec une forboucle ancienne . Je ne conteste pas qu'il y a une certaine différence dans «l'efficacité», mais je ne pense pas non plus que quiconque prétende que l'un a une magie que l'autre ne peut pas atteindre d'une manière ou d'une autre. Bien qu'il y ait, en fait, une chose qui mapne peut pas faire: ne pas retourner un tableau. Il y a une valeur à avoir JS hauteur des attentes des autres langues sur le comportement des favoris fonctionnels comme map, reduce, filter, etc. Par exemple, vous pouvez mettre en œuvre en reduceRightutilisant des blocs de construction similaires, mais pourquoi ne pas simplement utiliser reduceRight?
Ken Redler
1
@ChinotoVokro, votre exemple modifie explicitement le tableau d'origine en l'affectant b.val = b.val**2 à l'intérieur de l'objet retourné . C'est la chose précise que nous disons est en effet possible mais déroutante et non idiomatique. Normalement, vous renvoyez simplement la nouvelle valeur, pas également l'assignez au tableau d'origine:a=[{val:1},{val:2},{val:3},{val:4}]; a.map((b)=>{b.val**2}); JSON.stringify(a);
Ken Redler
66

La principale différence entre les deux méthodes est conceptuelle et stylistique: vous utilisez forEachlorsque vous voulez faire quelque chose sur ou avec chaque élément d'un tableau (faire "avec" est ce que le message que vous citez signifiait par "effets secondaires", je pense) , alors que vous utilisez maplorsque vous souhaitez copier et transformer chaque élément d'un tableau (sans changer l'original).

Parce que les deux mapet forEachappeler une fonction sur chaque élément dans un tableau, et cette fonction est définie par l' utilisateur, il n'y a presque rien que vous pouvez faire avec un seul et non pas avec l'autre. Il est possible, bien que laid, de l'utiliser mappour modifier un tableau sur place et / ou faire quelque chose avec des éléments de tableau:

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

mais beaucoup plus propre et plus évident quant à votre intention d'utiliser forEach:

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

Surtout si, comme c'est généralement le cas dans le monde réel, el est une variable lisible par l'homme:

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

De la même manière, vous pouvez facilement utiliser forEach pour créer un nouveau tableau:

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

mais c'est plus propre à utiliser map :

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

Notez également que, car map crée un nouveau tableau, il entraîne probablement au moins un certain impact sur les performances / la mémoire lorsque tout ce dont vous avez besoin est une itération, en particulier pour les grands tableaux - voir http://jsperf.com/map-foreach

Quant à savoir pourquoi vous souhaitez utiliser ces fonctions, elles sont utiles chaque fois que vous devez effectuer une manipulation de tableau en javascript, ce qui (même si nous ne parlons que de javascript dans un environnement de navigateur) est assez souvent, presque à tout moment vous accédez à un tableau que vous n'écrivez pas à la main dans votre code. Vous avez peut-être affaire à un tableau d'éléments DOM sur la page, à des données extraites d'une requête AJAX ou à des données saisies dans un formulaire par l'utilisateur. Un exemple courant que j'ai rencontré est l'extraction de données à partir d'une API externe, que vous voudrez peut-être utiliser mappour transformer les données dans le format souhaité, puis utiliser forEachpour itérer sur votre nouveau tableau afin de l'afficher à votre utilisateur.

Nrabinowitz
la source
Je vois des gens utiliser .map()tout le temps pour modifier des éléments en ligne. J'avais l'impression que c'était le principal avantage de l'utilisation de .mapover.forEach
chovy
1
@chovy Je pense que vous devriez choisir en fonction de votre volonté de créer un tableau de résultats. .mappeut modifier des éléments, oui, mais cela créerait un tableau dont vous n'avez pas besoin. Vous devriez également considérer comment le choix de l'un ou de l'autre permet au lecteur de deviner si vous avez ou non des effets secondaires
Leewz
16

La réponse votée (de Ken Redler) est trompeuse.

Un effet secondaire en informatique signifie qu'une propriété d'une fonction / méthode modifie un état global [wiki] . Dans un sens étroit, cela peut également inclure la lecture d'un état global, plutôt que d'arguments. Dans la programmation impérative ou OO, les effets secondaires apparaissent la plupart du temps. Et vous vous en servez probablement sans vous en rendre compte.

La différence significative entre forEachet mapest qu'elle mapalloue de la mémoire et stocke la valeur renvoyée, tout en la forEachrejetant. Voir les spécifications emca pour plus d'informations.

Quant à la raison pour laquelle les gens disent forEachest utilisé lorsque vous voulez un effet secondaire, c'est que la valeur de retour de forEachest toujours undefined. Si cela n'a pas d'effet secondaire (ne change pas l'état global), alors la fonction perd juste du temps CPU. Un compilateur optimisant éliminera ce bloc de code et le remplacera par la valeur finale (undefined ).

À propos, il convient de noter que JavaScript n'a aucune restriction sur les effets secondaires. Vous pouvez toujours modifier le tableau d'origine à l'intérieur 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
la source
1
Il est toujours amusant de revenir sur cette question, ses réponses et ses commentaires tous les deux ans. La façon dont la carte est implémentée dans JS, en termes d'allocation de mémoire, est un autre angle intéressant.
Ken Redler
6

C'est une belle question avec une réponse inattendue.

Ce qui suit est basé sur la description officielle deArray.prototype.map() .

Il n'y a rien qui forEach()puisse faire qui ne map()puisse pas. Autrement dit, map()est un super-ensemble strict de forEach().

Bien qu'il map()soit généralement utilisé pour créer un nouveau tableau, il peut également être utilisé pour changer le tableau actuel. L'exemple suivant illustre ceci:

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

Dans l'exemple ci-dessus, a aété commodément réglé de telle sorte que a[i] === ipour i < a.length. Même ainsi, il démontre la puissance map(), et en particulier sa capacité à changer le tableau sur lequel il est appelé.

Note1:
La description officielle implique que map()peut même changer la longueur du tableau sur lequel il est appelé! Cependant, je ne vois pas (une bonne) raison de faire cela.

Note 2:
Bien que map()map soit un super-ensemble de forEach(), forEach()doit toujours être utilisé là où l'on désire modifier un tableau donné. Cela rend vos intentions claires.

J'espère que cela a aidé.

Sumukh Barve
la source
8
En fait, il y a une chose que forEach peut faire que la carte ne peut pas faire - ne pas renvoyer un tableau.
Antti Haapala le
1
Vous pouvez également utiliser le troisième argument de la fonction de mappage pour muter le tableau cible, au lieu de la variable de portée:mapped = a.map(function (x, i, arr) { arr[i] = x * x * x; return x * x; });.
pdoherty926
@ pdoherty926 est vrai, mais il en est de même a.forEach(function(x, i, arr) { ..., ce qui, encore une fois, est une manière conceptuellement plus correcte lorsque vous avez l'intention de faire muter chaque élément d'origine plutôt que de mapper les valeurs d'un tableau à un autre
conny
@conny: D'accord. Veuillez également lire cette réponse à une question similaire.
Sumukh Barve
3

Vous pouvez utiliser mapcomme si c'était le cas forEach.

Cependant, il fera plus que nécessaire.

scopepeut être un objet arbitraire; ce n'est en aucun cas nécessairement this.

Quant à savoir s'il existe de réelles utilisations pour mapet forEach, ainsi que pour se demander s'il existe de réelles utilisations pour forou pour les whileboucles.

wombleton
la source
Mais pourquoi est-ce que "scope" et "this" sont appelés ici: func.call (scope, this [i], i, this); La portée n'est-elle pas un paramètre égal à l'objet actuel, qui est «ceci»?
JohnMerlino
Non, il peut être égal à l'objet actuel. L'objet lui-même est passé en tant que troisième paramètre au tableau. scope = scope || thissignifie "si scopeest faux (indéfini, nul, faux, etc.), définissez la portée sur à la thisplace et continuez".
wombleton
Pouvez-vous me relier à un exemple quand ce n'est pas égal à cela?
JohnMerlino
developer.mozilla.org/en/Core_JavaScript_1.5_Reference/… en a un sous "Impression du contenu d'un tableau avec une méthode objet"
wombleton
1

Bien que toutes les questions précédentes soient correctes, je ferais certainement une distinction différente. L'utilisation de mapet forEachpeut impliquer une intention.

J'aime utiliser maplorsque je transforme simplement les données existantes d'une manière ou d'une autre (mais que je veux m'assurer que les données d'origine restent inchangées.

J'aime utiliser forEachlorsque je modifie la collection en place.

Par exemple,

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

Ma règle de base est de m'assurer que lorsque vous mapcréez toujours un nouvel objet / valeur à retourner pour chaque élément de la liste source et de retourner plutôt que d'effectuer simplement une opération sur chaque élément.

À moins que vous n'ayez vraiment besoin de modifier la liste existante, cela n'a pas vraiment de sens de la modifier en place et s'intègre mieux dans les styles de programmation fonctionnels / immuables.

Maschwenk
la source
1

TL; réponse DR -

map renvoie toujours un autre tableau.

forEach non. C'est à vous de décider ce qu'il fait. Renvoyez un tableau si vous voulez ou faites autre chose si vous ne le faites pas.

La flexibilité est souhaitable dans certaines situations. Si ce n'est pas ce à quoi vous avez affaire, utilisez map.

charsi
la source
0

Encore une fois, je me sens comme un nécromancien ajoutant une réponse à une question qui a été posée il y a 5 ans (heureusement il y a une activité plutôt récente, donc je ne suis pas le seul à déranger les morts :).

D'autres ont déjà posté votre question principale concernant la différence entre les fonctions. Mais pour...

y a-t-il des utilisations réelles de ces méthodes en javascript (puisque nous ne mettons pas à jour une base de données) autre que de manipuler des nombres comme ceci:

... c'est drôle, tu devrais demander. Juste aujourd'hui, j'ai écrit un morceau de code qui attribue un certain nombre de valeurs d'une expression régulière à plusieurs variables en utilisant la carte pour la transformation. Il a été utilisé pour convertir une structure textuelle très compliquée en données visualisables ... mais par souci de simplicité, je vais offrir un exemple utilisant des chaînes de date, car celles-ci sont probablement plus familières pour tout le monde (cependant, si mon problème avait réellement été avec les dates , au lieu de la carte, j'aurais utilisé Date-object, qui aurait fait le travail à merveille tout seul).

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

Donc ... alors que ce n'était qu'un exemple - et n'a fait qu'une transformation très basique pour les données (juste à titre d'exemple) ... l'avoir fait sans carte aurait été une tâche beaucoup plus fastidieuse.

Certes, il est écrit dans une version de JavaScript que je ne pense pas que trop de navigateurs prennent encore en charge (au moins complètement) mais - nous y arrivons. Si j'avais besoin de l'exécuter dans un navigateur, je pense qu'il se transpilerait bien.

Markku Uttula
la source