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

135

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 peux pas comprendre comment cela fonctionne. Quelqu'un peut-il m'expliquer le comportement du tableau vide devant le forEach et comment cela callfonctionne?

Mimo
la source

Réponses:

203

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

Il est mis sur la page, car l'utilisation d'un tableau vous donne accès à des prototypes de tableau, comme .forEach.

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

Ensuite, forEachest une fonction qui prend une fonction comme entrée ...

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

... et pour chaque élément dans this(où thisest semblable à un tableau, en ce qu'il a un lengthet vous pouvez accéder à ses parties comme this[1]), il passera trois choses:

  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, .callest un prototype dont disposent les fonctions (c'est une fonction qui est appelée sur d'autres fonctions).
.callprendra son premier argument et remplacera l' thisintérieur de la fonction régulière par tout ce que vous avez passé call, comme premier argument ( undefinedou nullutilisera windowdans JS de tous les jours, ou sera ce que vous avez passé, si vous êtes en "mode strict"). Le reste des arguments sera transmis à 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 forEachfonction, et vous passez thisdu tableau vide à une liste de toutes les <a>balises, et pour chaque <a>ordre, vous appelez la fonction fournie.

ÉDITER

Conclusion logique / nettoyage

Ci-dessous, il y a un lien vers un article suggérant que nous abandonnons les tentatives de programmation fonctionnelle et que nous nous en tenons à la boucle manuelle en ligne, à chaque fois, car cette solution est hack-ish et inesthétique.

Je dirais que tout .forEachest moins utile que ses homologues, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), il sert toujours quand des fins tout ce que vous voulez vraiment faire est de modifier le monde extérieur ( et non le tableau), le n-temps, tout en ayant accès à une ou l' autre arr[i]ou i.

Alors, comment gérer la disparité, car Motto est 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 fois c'est l'apprentissage tête première)?

La réponse est en fait assez simple, et quelque chose que l'oncle Bob et Sir Crockford feraient tous les deux facepalm, en raison de l'oubli:

nettoyez-le .

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 faire cela, vous-même, la réponse pourrait bien être non ...
Cette chose exacte est faite par ... ... chaque (?) Bibliothèque avec des fonctionnalités d'ordre supérieur ces jours-ci.
Si vous utilisez lodash ou un trait de soulignement ou même jQuery, ils auront tous un moyen de prendre un ensemble d'éléments et d'exécuter une action n fois.
Si vous n'utilisez pas une telle chose, alors écrivez 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à

Non seulement une méthode d'assistance slice( )/ array( )/ etc va faciliter la vie des personnes qui souhaitent utiliser des listes comme elles utilisent des tableaux (comme elles le devraient), mais aussi pour les personnes qui ont le luxe de fonctionner dans les navigateurs ES6 + du relativement proche futur, ou de "transpilage" dans Babel aujourd'hui, vous avez des fonctionnalités de langage intégrées, qui rendent ce type de chose inutile.

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érateurs Rest et Spread ; essayez-les sur le site BabelJS; si votre pile technologique est en ordre, utilisez-les en production avec Babel et une étape de construction.


Il n'y a aucune bonne raison de ne pas pouvoir utiliser la transformation de non-tableau en tableau ... ... ne faites pas un gâchis de votre code en ne faisant rien d'autre que coller cette même ligne laide, partout.

Norguard
la source
Maintenant, je suis un peu confus car si je l'ai fait: console.log (ceci); J'obtiendrai toujours l'objet window Et je pensais que .call change la valeur de ceci
Muhammad Saleh
.call ne change la valeur this, ce qui est la raison pour laquelle vous utilisez callle forEach. Si forEach ressemble forEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }alors en changeant thisen disant forEach.call( newArrLike )signifie qu'il utilisera ce nouvel objet comme this.
Norguard
2
@MuhammadSaleh .callne change pas la valeur de thisinside de ce à quoi vous passez forEach, .callchange la valeur de this inside de forEach . Si vous ne savez pas pourquoi il thisn'est pas transmis forEachà la fonction que vous vouliez appeler, vous devriez rechercher le comportement de thisdans JavaScript. En complément, applicable uniquement ici (et map / filter / reduction), il y a un deuxième argument à forEach, qui définit thisà l'intérieur de la fonction arr.forEach(fn, thisInFn)ou [].forEach.call(arrLike, fn, thisInFn);ou simplement use .bind; arr.forEach(fn.bind(thisInFn));
Norguard
1
@thetrystero c'est exactement le point. []est juste là en tant que tableau, de sorte que vous puissiez .callla .forEachméthode, et changez le thissur l'ensemble sur lequel vous voulez travailler. .callressemble à ceci: function (thisArg, a, b, c) { var method = this; method(a, b, c); }sauf qu'il y a de la magie noire interne à définir à l' thisintérieur de methodpour égaler ce que vous avez passé thisArg.
Norguard
1
donc en bref ... Array.from ()?
zakius
53

La querySelectorAllméthode renvoie a NodeList, qui est similaire à un tableau, mais ce n'est pas tout à fait un tableau. Par conséquent, il n'a pas de forEachméthode (dont les objets de tableau héritent via Array.prototype).

Puisque a NodeListest similaire à un tableau, les méthodes de tableau fonctionneront réellement dessus, donc en utilisant [].forEach.callvous invoquez la Array.prototype.forEachméthode dans le contexte de NodeList, comme si vous aviez pu le faire simplement yourNodeList.forEach(/*...*/).

Notez que le littéral de tableau vide n'est qu'un raccourci vers la version étendue, que vous verrez probablement aussi assez souvent:

Array.prototype.forEach.call(/*...*/);
James Allardice
la source
7
Donc, pour clarifier, il n'y a aucun avantage à utiliser [].forEach.call(['a','b'], cb)over ['a','b'].forEach(cb)dans les applications quotidiennes avec des tableaux standard, juste en essayant d'itérer sur des structures de type tableau qui n'ont pas forEachleur propre prototype? Est-ce exact?
Matt Fletcher
2
@MattFletcher: Oui, c'est exact. Les deux fonctionneront mais pourquoi trop compliquer les choses? Appelez simplement la méthode directement sur le tableau lui-même.
James Allardice
Cool merci. Et je ne sais pas pourquoi, peut-être juste pour le crédit de la rue, impressionner les vieilles dames d'ALDI et autres. Je m'en tiendrai à [].forEach():)
Matt Fletcher
var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) fonctionne bien
daslicht
@daslicht pas dans les anciennes versions d'IE! (qui ne , cependant, appuyer forEachsur les tableaux, mais pas des objets NodeList)
Djave
21

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

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

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

Vous pouvez maintenant appeler cette fonction au lieu du code plus compliqué et obscur:

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

Il peut être mieux écrit en utilisant

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

});

Cela document.querySelectorAll('a')renvoie un objet similaire à un tableau, mais il n'hérite pas du Arraytype. Nous appelons donc la forEachméthode à partir de l' Array.prototypeobjet avec le contexte comme valeur renvoyée pardocument.querySelectorAll('a')

Arun P Johny
la source
4
[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

C'est fondamentalement la même chose que:

var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
   // whatever with the current node
});
vava
la source
2

Vous voulez mettre à jour cette ancienne question:

La raison d'utiliser [].foreach.call()pour parcourir des éléments dans les 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 en utilisant 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.

František Heča
la source
1

Un tableau vide a une propriété forEachdans son prototype qui est un objet Function. (Le tableau vide est juste un moyen facile d'obtenir une référence à la forEachfonction que tous les Arrayobjets ont.) Les objets fonction, à leur tour, ont une callpropriété qui est également une fonction. Lorsque vous appelez la fonction d'une callfonction, elle exécute la fonction avec les arguments donnés. Le premier argument devient thisdans la fonction appelée.

Vous pouvez trouver la documentation de la callfonction ici . La documentation pour forEachest ici .

Ted Hopp
la source
1

Ajoutez simplement une ligne:

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

Et voila!

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

Prendre plaisir :-)

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

imos
la source
1

Juste une solution rapide et sale que je finis toujours par utiliser. Je ne toucherais pas aux prototypes, tout comme une bonne pratique. Bien sûr, il existe de nombreuses façons d'améliorer les choses, 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}`);
});
Alfredo Maria Milano
la source
0

[]renvoie toujours un nouveau tableau, il équivaut à new Array()mais il est garanti de renvoyer un tableau car il Arraypourrait être écrasé par l'utilisateur alors que []ne le peut pas. C'est donc un moyen sûr d'obtenir le prototype de Array, puis, comme décrit, callest utilisé pour exécuter la fonction sur la liste de nœuds arraylike (this).

Appelle une fonction avec une valeur donnée et des arguments fournis individuellement. mdn

Xotic750
la source
Alors []qu'en fait, il renverra toujours un tableau, alors qu'il window.Arraypeut être écrasé par n'importe quoi (dans tous les anciens navigateurs), il peut également window.Array.prototype.forEachêtre écrasé par n'importe quoi. Il n'y a aucune garantie de sécurité dans les deux cas, dans les navigateurs qui ne prennent pas en charge les getters / setters ou le scellage / gel d'objets (le gel provoquant ses propres problèmes d'extensibilité).
Norguard
Ne hésitez pas à modifier la réponse d'ajouter les informations supplémentaires en ce qui concerne la possibilité d'écraser les prototypeou les méthodes de elle: forEach.
Xotic750
0

Norguard a expliqué CE QUE [].forEach.call() fait et James Allardice POURQUOI nous le faisons: parce que querySelectorAll renvoie un NodeListqui 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);
        }
    };
}
Mikel
la source
0

Beaucoup de bonnes informations sur cette page (voir réponse + réponse + commentaire ), mais j'ai récemment eu la même question que l'OP, et il a fallu un peu de fouille pour avoir une vue d'ensemble. Alors, voici une version courte:

Le but est d'utiliser des Arrayméthodes sur un tableau NodeListqui n'a pas ces méthodes lui-même.

Un modèle plus ancien a coopté les méthodes de Array via Function.call()et a utilisé un littéral de tableau ( []) plutôt que Array.prototypeparce qu'il était plus court à taper:

[].forEach.call(document.querySelectorAll('a'), a => {})

Un modèle plus récent (post ECMAScript 2015) consiste à utiliser Array.from():

Array.from(document.querySelectorAll('a')).forEach(a => {})
Ellemenno
la source