web-dev-qa-db-fra.com

Que fait [] .forEach.call () en JavaScript?

Je regardais des extraits de code et j'ai trouvé plusieurs éléments appelant une fonction sur une liste de nœuds avec un forEach appliqué à un tableau vide.

Par exemple, j'ai quelque chose comme:

[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

mais je ne comprends pas comment ça marche. Quelqu'un peut-il m'expliquer le comportement du tableau vide devant le forEach et comment fonctionne le call?

104
Mimo

[] est un tableau.
Ce tableau n'est pas utilisé du tout. 

Il est placé sur la page, car l'utilisation d'un tableau vous donne accès à des prototypes de tableaux, tels que .forEach.

Ceci est juste plus rapide que de taper Array.prototype.forEach.call(...);

Ensuite, forEach est une fonction qui prend une fonction en tant qu'entrée ...

[1,2,3].forEach(function (num) { console.log(num); });

... et pour chaque élément dans this (où this est semblable à un tableau, en ce sens qu'il a une length et que vous pouvez accéder à ses parties comme this[1]), trois éléments sont transmis:

  1. l'élément dans le tableau
  2. l'index de l'élément (le troisième élément passerait 2)
  3. une référence au tableau

Enfin, .call est un prototype dont les fonctions ont (c’est une fonction qui est appelée sur d’autres fonctions).
.call prendra son premier argument et remplacera this à l'intérieur de la fonction normale par tout ce que vous avez passé call, en tant que premier argument (undefined ou null utilisera window dans JS de tous les jours, ou sera ce que vous avez passé, si vous utilisez "strict- mode"). Le reste des arguments sera passé à la fonction d'origine. 

[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
    console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"

Par conséquent, vous créez un moyen rapide d'appeler la fonction forEach et vous remplacez this du tableau vide par une liste de toutes les balises <a> et, pour chaque <a> dans l'ordre, vous appelez la fonction fournie.

MODIFIER

Conclusion logique/nettoyage

Ci-dessous, vous trouverez un lien vers un article suggérant d'abandonner les tentatives de programmation fonctionnelle et de nous en tenir à la boucle manuelle en boucle, car cette solution est bidon et inesthétique. 

Je dirais que bien que .forEach soit moins utile que ses homologues, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), il sert toujours à quelque chose lorsque vous ne voulez vraiment que modifier le monde extérieur (pas le tableau), n-fois, tout en ayant accès à arr[i] ou i.

Alors, comment pouvons-nous gérer cette disparité, Motto étant clairement un gars talentueux et compétent, et j'aimerais imaginer que je sais ce que je fais/où je vais (de temps en temps ... ... autre Parfois, c'est l'apprentissage tête-à-tête)?

La réponse est en fait assez simple, et quelque chose qu'Oncle Bob et Sir Crockford auraient tous deux facepalm, en raison de la négligence:

nettoie ça.

function toArray (arrLike) { // or asArray(), or array(), or *whatever*
  return [].slice.call(arrLike);
}

var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);

Maintenant, si vous vous demandez si vous avez besoin de le faire vous-même, la réponse pourrait bien être non ...
Cette chose exacte est faite par ... ... chaque (?) Bibliothèque avec des fonctions d'ordre supérieur de nos jours.
Si vous utilisez lodash ou underscore ou même jQuery, ils auront tous le moyen de prendre un ensemble d’éléments et d’effectuer une action n fois.
Si vous n'utilisez pas une telle chose, alors écrivez bien la vôtre.

lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
  var others = lib.array(arguments, 1);
  return others.reduce(appendKeys, subject);
};

Mise à jour pour ES6 (ES2015) et au-delà

Une méthode d'assistance slice( )/array( )/etc va faciliter la vie des personnes qui souhaitent utiliser des listes exactement comme elles utilisent des tableaux (comme elles le devraient), mais aussi des personnes qui ont le luxe de pouvoir utiliser ES6 +. -bien près de l'avenir, ou de "transpiler" dans Babel aujourd'hui, vous avez des fonctionnalités de langage intégrées, qui rendent ce genre de choses inutiles.

function countArgs (...allArgs) {
  return allArgs.length;
}

function logArgs (...allArgs) {
  return allArgs.forEach(arg => console.log(arg));
}

function extend (subject, ...others) { /* return ... */ }


var nodeArray = [ ...nodeList1, ...nodeList2 ];

Super propre et très utile.
Recherchez les opérateursresteetécartés; essayez-les sur le site BabelJS; Si votre pile technologique est en ordre, utilisez-la dans la production avec Babel et une étape de construction.


Il n'y a pas de bonne raison de ne pas pouvoir utiliser la transformation de non-tableau en tableau ... ... juste ne faites pas gâcher votre code sans rien faire mais coller cette même ligne laide, partout .

168
Norguard

La méthode querySelectorAll renvoie une NodeList, semblable à un tableau, mais ce n'est pas tout à fait un tableau. Par conséquent, il n'a pas de méthode forEach (dont les objets de tableau héritent via Array.prototype).

Etant donné qu'une NodeList est similaire à un tableau, les méthodes de tableau fonctionneront réellement. Par conséquent, en utilisant [].forEach.call, vous appelez la méthode Array.prototype.forEach dans le contexte de NodeList, comme si vous pouviez simplement faire yourNodeList.forEach(/*...*/).

Notez que le littéral de tableau vide est juste un raccourci vers la version développée, que vous verrez probablement aussi très souvent:

Array.prototype.forEach.call(/*...*/);
43
James Allardice

Les autres réponses ont très bien expliqué ce code, je vais donc ajouter une suggestion.

Ceci est un bon exemple de code qui devrait être refactoré pour plus de simplicité et de clarté. Au lieu d'utiliser [].forEach.call() ou Array.prototype.forEach.call() à chaque fois que vous faites cela, créez-en une fonction simple:

function forEach( list, callback ) {
    Array.prototype.forEach.call( list, callback );
}

Vous pouvez maintenant appeler cette fonction à la place du code plus compliqué et obscur:

forEach( document.querySelectorAll('a'), function( el ) {
   // whatever with the current node
});
19
Michael Geary

Il peut être mieux écrit en utilisant

Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {

});

document.querySelectorAll('a') renvoie un objet similaire à un tableau, mais il n'hérite pas du type Array . Nous appelons donc la méthode forEach à partir de l'objet Array.prototype avec le contexte comme valeur renvoyée par document.querySelectorAll('a').

4
Arun P Johny

Juste une solution rapide et sale que je finis toujours par utiliser. Je ne toucherais pas aux prototypes, c'est tout aussi pratique. Bien sûr, il y a beaucoup de façons d'améliorer cela, mais vous voyez l'idée.

const forEach = (array, callback) => {
  if (!array || !array.length || !callback) return
  for (var i = 0; i < array.length; i++) {
    callback(array[i], i);
  }
}

forEach(document.querySelectorAll('.a-class'), (item, index) => {
  console.log(`Item: ${item}, index: ${index}`);
});
1

Ajoutez juste une ligne:

NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;

Et le tour est joué!

document.querySelectorAll('a').forEach(function(el) {
  // whatever with the current node
});

Prendre plaisir :-)

Attention: NodeList est une classe globale. N'utilisez pas cette recommandation si vous écrivez une bibliothèque publique. Toutefois, c’est un moyen très pratique d’augmenter l’auto-efficacité lorsque vous travaillez sur un site Web ou une application node.js.

1
imos

Un tableau vide a une propriété forEach dans son prototype qui est un objet Function. (Le tableau vide est simplement un moyen simple d'obtenir une référence à la fonction forEach que possèdent tous les objets Array.) Les objets Function possèdent à leur tour une propriété call qui est également une fonction. Lorsque vous appelez la fonction call d'une fonction, celle-ci est exécutée avec les arguments donnés. Le premier argument devient this dans la fonction appelée.

Vous pouvez trouver la documentation de la fonction callici . La documentation pour forEach est ici .

1
Ted Hopp

[] renvoie toujours un nouveau tableau, il est équivalent à new Array() mais il est garanti qu'il renverra un tableau car Array pourrait être écrasé par l'utilisateur alors que [] ne le pourrait pas. Il s’agit donc d’un moyen sûr d’obtenir le prototype de Array; alors, comme décrit ci-dessus, call est utilisé pour exécuter la fonction sur la liste de noeuds arraylike (this).

Appelle une fonction avec cette donnée et les arguments fournis individuellement. mdn

0
Xotic750

Voulez-vous mettre à jour sur cette vieille question:

La raison d'utiliser [].foreach.call() pour parcourir en boucle les éléments des navigateurs modernes est généralement révolue. Nous pouvons utiliser document.querySelectorAll("a").foreach() directement.

Les objets NodeList sont des collections de nœuds, généralement renvoyés par des propriétés telles que Node.childNodes et des méthodes telles que document.querySelectorAll ().

Bien que NodeList ne soit pas un tableau, il est possible de le parcourir avec forEach () . Il peut également être converti en un véritable tableau à l'aide de Array.from ().

Cependant, certains navigateurs plus anciens n'ont pas implémenté NodeList.forEach () ni Array.from (). Cela peut être contourné en utilisant Array.prototype.forEach () - voir l'exemple de ce document.

0
František Heča

Norguard a expliqué CE QUE[].forEach.call() fait et James AllardicePOURQUOI on le fait: parce que querySelectorAll renvoie une NodeList qui n'a pas de méthode forEach ...

Sauf si vous avez un navigateur moderne comme Chrome 51+, Firefox 50+, Opera 38, Safari 10.

Sinon, vous pouvez ajouter un Polyfill :

if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (var i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}
0
Mikel