Cosa fa [] .forEach.call () in JavaScript?


135

Stavo guardando alcuni frammenti di codice e ho trovato più elementi che chiamavano una funzione su un elenco di nodi con un forEach applicato a un array vuoto.

Ad esempio ho qualcosa come:

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

ma non riesco a capire come funziona. Qualcuno può spiegarmi il comportamento dell'array vuoto davanti a forEach e come callfunziona?

Risposte:


203

[]è un array.
Questo array non è affatto utilizzato.

Viene messo sulla pagina, perché l'uso di un array ti dà accesso a prototipi di array, come .forEach.

Questo è solo più veloce della digitazione Array.prototype.forEach.call(...);

Successivamente, forEachè una funzione che accetta una funzione come input ...

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

... e per ogni elemento in this(dove thisè simile ad array, in quanto ha un lengthe puoi accedere alle sue parti come this[1]) passerà tre cose:

  1. l'elemento nella matrice
  2. l'indice dell'elemento (passerebbe il terzo elemento 2)
  3. un riferimento alla matrice

Infine, .callè un prototipo che le funzioni hanno (è una funzione che viene chiamata su altre funzioni).
.callprenderà il suo primo argomento e sostituirà thisla normale funzione con qualunque cosa tu abbia passato call, come il primo argomento ( undefinedo nulluserà windowin JS di tutti i giorni, o sarà qualunque cosa tu abbia passato, se in "modalità rigorosa"). Il resto degli argomenti verrà passato alla funzione originale.

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

Pertanto, stai creando un modo rapido per chiamare la forEachfunzione e stai passando thisdall'array vuoto a un elenco di tutti i <a>tag e per ciascuno <a>in ordine, stai chiamando la funzione fornita.

MODIFICARE

Conclusione logica / Pulizia

Di seguito, c'è un link a un articolo che suggerisce che eliminiamo i tentativi di programmazione funzionale e ci atteniamo sempre al loop manuale, in linea, perché questa soluzione è hack-ish e antiestetica.

Direi che, mentre .forEachè meno utile rispetto ai suoi omologhi, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), serve ancora scopi quando tutti si vuole veramente fare è modificare il mondo esterno (non l'array), n-volte, pur avendo accesso a uno arr[i]o i.

Quindi, come possiamo gestire la disparità, dato che Motto è chiaramente un ragazzo di talento e ben informato, e vorrei immaginare di sapere cosa sto facendo / dove sto andando (ora e poi ... ... altro volte è il primo apprendimento)?

La risposta è in realtà abbastanza semplice e qualcosa a cui lo zio Bob e Sir Crockford avrebbero dovuto far fronte entrambi, a causa della svista:

puliscilo .

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

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

Ora, se ti stai chiedendo se devi farlo tu stesso, la risposta potrebbe non essere ...
Questa cosa esatta è fatta da ... ... ogni (?) Libreria con caratteristiche di ordine superiore in questi giorni.
Se stai usando lodash o underscore o persino jQuery, avranno tutti un modo per prendere un insieme di elementi ed eseguire un'azione n-volte.
Se non stai usando una cosa del genere, allora scrivilo tu stesso.

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

Aggiornamento per ES6 (ES2015) e oltre

Non solo un metodo di supporto slice( )/ array( )/ etc renderà la vita più facile per le persone che vogliono usare gli elenchi proprio come usano gli array (come dovrebbero), ma per le persone che hanno il lusso di operare in browser ES6 + relativamente vicini futuro, o di "traspiling" in Babel oggi, hai funzionalità linguistiche integrate, che rendono questo tipo di cose inutili.

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-pulito e molto utile.
Cerca gli operatori Rest e Spread ; provali sul sito BabelJS; se il tuo stack tecnologico è in ordine, usali in produzione con Babel e un passaggio di costruzione.


Non c'è alcun buon motivo per non essere in grado di usare la trasformazione da non-array in array ... ... semplicemente non fare confusione con il tuo codice non facendo altro che incollare la stessa brutta linea, ovunque.


Ora sono un po 'confuso se l'ho fatto: console.log (questo); Prenderò sempre l'oggetto finestra E ho pensato che .call cambia il valore di questo
Muhammad Saleh,

.call non modificare il valore di this, ed è per questo che si sta utilizzando callcon il foreach. Se forEach appare come forEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }allora cambiando thisdicendo forEach.call( newArrLike )significa che utilizzerà quel nuovo oggetto come this.
Norguard,

2
@MuhammadSaleh .callnon cambia il valore di thisdentro ciò a cui passi forEach, .callcambia il valore dithis inside di forEach . Se sei confuso sul perché thisnon passi dalla forEachfunzione che volevi chiamare, dovresti cercare il comportamento di thisin JavaScript. Come addendum, applicabile solo qui (e mappa / filtro / riduci), c'è un secondo argomento a forEach, che si trova thisall'interno della funzione arr.forEach(fn, thisInFn)o [].forEach.call(arrLike, fn, thisInFn);o semplicemente usa .bind; arr.forEach(fn.bind(thisInFn));
Norguard,

1
@thetrystero questo è esattamente il punto. []è proprio lì come un array, in modo che tu possa .callil .forEachmetodo e cambiare thisil set sul quale vuoi lavorare. .callassomiglia a questo: function (thisArg, a, b, c) { var method = this; method(a, b, c); }tranne per il fatto che c'è una magia nera interna da mettere thisdentro methodper eguagliare ciò che hai passato thisArg.
Norguard,

1
quindi in breve ... Array.from ()?
zakius,

53

Il querySelectorAllmetodo restituisce a NodeList, che è simile a un array, ma non è proprio un array. Pertanto, non ha un forEachmetodo (che gli oggetti array ereditano tramite Array.prototype).

Poiché a NodeListè simile a un array, i metodi dell'array funzioneranno effettivamente su di esso, quindi usando [].forEach.callte stai invocando il Array.prototype.forEachmetodo nel contesto di NodeList, come se fossi stato in grado di farlo semplicemente yourNodeList.forEach(/*...*/).

Si noti che l'array vuoto letterale è solo un collegamento alla versione espansa, che probabilmente vedrai anche abbastanza spesso:

Array.prototype.forEach.call(/*...*/);

7
Quindi, per chiarire, non c'è alcun vantaggio nell'utilizzo di [].forEach.call(['a','b'], cb)sopra ['a','b'].forEach(cb)in applicazioni di uso quotidiano con gli array standard, proprio quando si cerca di iterare array come le strutture che non hanno forEachil proprio prototipo? È corretto?
Matt Fletcher,

2
@MattFletcher: Sì, è corretto. Entrambi funzioneranno, ma perché complicare le cose? Basta chiamare il metodo direttamente sull'array stesso.
James Allardice,

Figo, grazie. E non so perché, forse solo per il credito di strada, impressionare le donne anziane in ALDI e simili. Mi atterrò a [].forEach():)
Matt Fletcher il

var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) funziona bene
daslicht,

@daslicht non nelle versioni precedenti di IE! (che fanno invece supportare forEachsu array, ma non oggetti NodeList)
Djave

21

Le altre risposte hanno spiegato molto bene questo codice, quindi aggiungerò solo un suggerimento.

Questo è un buon esempio di codice che dovrebbe essere refactored per semplicità e chiarezza. Invece di usare [].forEach.call()o Array.prototype.forEach.call()ogni volta che lo fai, creane una semplice funzione:

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

Ora puoi chiamare questa funzione invece del codice più complicato e oscuro:

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

5

Può essere scritto meglio usando

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

});

Ciò che fa è document.querySelectorAll('a')restituire un oggetto simile a un array, ma non eredita dal Arraytipo. Quindi chiamiamo il forEachmetodo Array.prototypedall'oggetto con il contesto come valore restituito dadocument.querySelectorAll('a')


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

È sostanzialmente lo stesso di:

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

2

Vuoi aggiornare su questa vecchia domanda:

Il motivo da utilizzare [].foreach.call()per scorrere gli elementi nei browser moderni è per lo più finito. Possiamo usare document.querySelectorAll("a").foreach()direttamente.

Gli oggetti NodeList sono raccolte di nodi, generalmente restituite da proprietà come Node.childNodes e metodi come document.querySelectorAll ().

Sebbene NodeList non sia un array, è possibile iterare su di esso con forEach () . Può anche essere convertito in un vero array usando Array.from ().

Tuttavia, alcuni browser meno recenti non hanno implementato NodeList.forEach () né Array.from (). Questo può essere aggirato usando Array.prototype.forEach () - vedere l'esempio di questo documento.


1

Un array vuoto ha una proprietà forEachnel suo prototipo che è un oggetto Function. (L'array vuoto è solo un modo semplice per ottenere un riferimento alla forEachfunzione che hanno tutti gli Arrayoggetti.) Gli oggetti funzione, a loro volta, hanno una callproprietà che è anche una funzione. Quando si richiama la funzione di una callfunzione, viene eseguita la funzione con gli argomenti forniti. Il primo argomento diventathis nella funzione chiamata.

È possibile trovare la documentazione per la callfunzione qui . La documentazione per forEachè qui .


1

Aggiungi solo una riga:

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

E voilà!

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

Godere :-)

Avvertenza: NodeList è una classe globale. Non usare questa raccomandazione se stai scrivendo una biblioteca pubblica. Tuttavia è un modo molto conveniente per aumentare l'autoefficacia quando lavori sul sito Web o sull'app node.js.


1

Solo una soluzione rapida e sporca che finisco sempre per usare. Non toccherei i prototipi, ma altrettanto buona pratica. Certo, ci sono molti modi per renderlo migliore, ma hai l'idea.

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

0

[]restituisce sempre un nuovo array, è equivalente a new Array()ma è garantito per restituire un array perché Arraypotrebbe essere sovrascritto dall'utente mentre []non può. Quindi questo è un modo sicuro per ottenere il prototipo di Array, quindi, come descritto, callviene utilizzato per eseguire la funzione nella lista dei nodi arraylike (questo).

Chiama una funzione con un dato valore e argomenti forniti singolarmente. MDN


Mentre []in realtà restituirà sempre un array, mentre window.Arraypuò essere sovrascritto con qualsiasi cosa (in tutti i vecchi browser), quindi anche può window.Array.prototype.forEachessere sovrascritto con qualsiasi cosa. Non esiste alcuna garanzia di sicurezza in entrambi i casi, nei browser che non supportano getter / setter o sigillatura / congelamento degli oggetti (il congelamento causa i propri problemi di estensibilità).
Norguard,

Sentitevi liberi di modificare la risposta per aggiungere le informazioni supplementari in merito alla possibilità di sovrascrivere i prototypeo è metodi: forEach.
Xotic750,

0

Norguard ha spiegato COSA [].forEach.call() fa e James Allardice PERCHÉ lo facciamo: perché querySelectorAll restituisce un NodeListche non ha un metodo forEach ...

A meno che tu non abbia un browser moderno come Chrome 51+, Firefox 50+, Opera 38, Safari 10.

Altrimenti puoi aggiungere 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

Molte buone informazioni su questa pagina (vedi risposta + risposta + commento ), ma di recente ho avuto la stessa domanda del PO, e ci sono voluti degli scavi per ottenere l'intera immagine. Quindi, ecco una versione breve:

L'obiettivo è utilizzare Arraymetodi su un array simile NodeListche non ha questi metodi stessi.

Un modello più vecchio cooptava i metodi di Array tramite Function.call()e utilizzava un array letterale ( []) piuttosto che Array.prototypeperché era più breve da digitare:

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

Un modello più recente (post ECMAScript 2015) è quello di utilizzare Array.from():

Array.from(document.querySelectorAll('a')).forEach(a => {})
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.