Was macht [] .forEach.call () in JavaScript?

134

Ich habe mir einige Codeausschnitte angesehen und mehrere Elemente gefunden, die eine Funktion über eine Knotenliste aufrufen, wobei forEach auf ein leeres Array angewendet wurde.

Zum Beispiel habe ich so etwas wie:

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

aber ich kann nicht verstehen, wie es funktioniert. Kann mir jemand das Verhalten des leeren Arrays vor dem forEach erklären und wie das callfunktioniert?

Mimo
quelle

Antworten:

202

[]ist ein Array.
Dieses Array wird überhaupt nicht verwendet.

Es wird auf die Seite gestellt, da Sie durch die Verwendung eines Arrays Zugriff auf Array-Prototypen wie z .forEach.

Dies ist nur schneller als das Tippen Array.prototype.forEach.call(...);

Als nächstes forEachist eine Funktion, die eine Funktion als Eingabe nimmt ...

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

... und für jedes Element in this(wo thises Array-ähnlich ist, indem es ein hat lengthund Sie auf seine Teile wie zugreifen können this[1]) werden drei Dinge übergeben:

  1. das Element im Array
  2. der Index des Elements (drittes Element würde passieren 2)
  3. ein Verweis auf das Array

Schließlich .callist ein Prototyp, den Funktionen haben (es ist eine Funktion, die für andere Funktionen aufgerufen wird).
.callnimmt das erste Argument und ersetzt es thisinnerhalb der regulären Funktion durch das, was Sie callals erstes Argument übergeben haben ( undefinedoder nullwird windowim alltäglichen JS verwendet oder ist das, was Sie übergeben haben, wenn es sich im "strengen Modus" befindet). Die restlichen Argumente werden an die ursprüngliche Funktion übergeben.

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

Daher erstellen Sie eine schnelle Möglichkeit, die forEachFunktion aufzurufen , und wechseln thisvom leeren Array zu einer Liste aller <a>Tags. Für jede <a>Reihenfolge rufen Sie die bereitgestellte Funktion auf.

BEARBEITEN

Logische Schlussfolgerung / Bereinigung

Unten finden Sie einen Link zu einem Artikel, der darauf hinweist, dass wir Versuche der funktionalen Programmierung abschaffen und uns jedes Mal an manuelle Inline-Schleifen halten, da diese Lösung hackig und unansehnlich ist.

Ich würde sagen , dass , während .forEachweniger hilfreich als seine Kollegen, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), es dient immer noch Zwecke , wenn alles , was Sie wirklich wollen , ist die Außenwelt ändern (nicht das Array), n-mal, während Zugang zu entweder arr[i]oder i.

Wie gehen wir mit der Ungleichheit um, da Motto eindeutig ein talentierter und sachkundiger Typ ist und ich mir vorstellen möchte, dass ich weiß, was ich tue / wohin ich gehe (ab und zu ... ... andere) Mal ist es Head-First-Lernen)?

Die Antwort ist eigentlich recht einfach, und Onkel Bob und Sir Crockford würden aufgrund des Versehens beide Gesichtspalmen:

räum es auf .

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

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

Wenn Sie sich jetzt fragen, ob Sie dies selbst
tun müssen, lautet die Antwort möglicherweise nein ... Genau das erledigt heutzutage ... ... jede (?) Bibliothek mit Funktionen höherer Ordnung.
Wenn Sie lodash oder Unterstrich oder sogar jQuery verwenden, haben alle die Möglichkeit, eine Reihe von Elementen zu übernehmen und eine Aktion n-mal auszuführen.
Wenn Sie so etwas nicht verwenden, schreiben Sie auf jeden Fall Ihre eigenen.

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

Update für ES6 (ES2015) und darüber hinaus

Eine slice( )/ array( )/ etc-Hilfsmethode wird nicht nur Menschen das Leben erleichtern, die Listen genauso verwenden möchten, wie sie Arrays verwenden (wie sie sollten), sondern auch Menschen, die den Luxus haben, in ES6 + -Browsern der relativ nahen Umgebung zu arbeiten Zukunft oder von "Transpilieren" in Babel heute haben Sie Sprachfunktionen eingebaut, die diese Art von Dingen unnötig machen.

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 sauber und sehr nützlich.
Suchen Sie nach den Operatoren Rest und Spread . Probieren Sie sie auf der BabelJS-Website aus. Wenn Ihr Tech-Stack in Ordnung ist, verwenden Sie ihn in der Produktion mit Babel und einem Build-Schritt.


Es gibt keinen guten Grund , nicht in der Lage sein , die aus Nicht-Array in einem Array umwandeln zu verwenden ... ... macht nicht nur ein Durcheinander von Code nichts zu tun , aber der gleichen hässlicher Linie einfügen, überall.

Norguard
quelle
Jetzt bin ich ein bisschen verwirrt, weil ich es getan habe: console.log (this); Ich werde immer das Fensterobjekt bekommen Und ich dachte, dass .call den Wert dieses ändert
Muhammad Saleh
.call nicht ändern den Wert this, weshalb Sie verwenden callmit dem forEach. Wenn forEach so aussieht , bedeutet das forEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }Ändern thisdurch Sagen forEach.call( newArrLike ), dass das neue Objekt als verwendet wird this.
Norguard
2
@MuhammadSaleh .callnicht den Wert der Änderung thisinnerhalb dessen , was Sie passieren zu forEach, .calländert sich der Wert von this innen forEach . Wenn Sie sich als zu verwechseln sind , warum thisnicht weitergegeben Bekommt forEachin die Funktion , die Sie genannt wollten, dann sollten Sie das Verhalten nachschlagen thisin JavaScript. Als Zusatz, anwendbar nur hier (und Karte / Filter / verringern), ist ein zweites Argument forEach, die Sätze thisin der Funktion arr.forEach(fn, thisInFn)oder [].forEach.call(arrLike, fn, thisInFn);oder Verwendung .bind; arr.forEach(fn.bind(thisInFn));
Norguard
1
@thetrystero das ist genau der Punkt. []ist nur als Array vorhanden, damit Sie .calldie .forEachMethode verwenden und die thiszu dem Satz ändern können, an dem Sie arbeiten möchten. .callsieht wie folgt aus : function (thisArg, a, b, c) { var method = this; method(a, b, c); }außer , dass es interne schwarz-Magie - Set thisinnerhalb der methodgleich , was Sie übergeben , wie thisArg.
Norguard
1
also kurz ... Array.from ()?
Zakius
53

Die querySelectorAllMethode gibt ein zurück NodeList, das einem Array ähnelt, aber kein Array ist. Daher gibt es keine forEachMethode (über die Array-Objekte erben Array.prototype).

Da a NodeListeinem Array ähnlich ist, funktionieren Array-Methoden tatsächlich damit. Wenn Sie also verwenden [].forEach.call, rufen Sie die Array.prototype.forEachMethode im Kontext von auf NodeList, als ob Sie dies einfach hätten tun können yourNodeList.forEach(/*...*/).

Beachten Sie, dass das leere Array-Literal nur eine Verknüpfung zur erweiterten Version ist, die Sie wahrscheinlich auch häufig sehen werden:

Array.prototype.forEach.call(/*...*/);
James Allardice
quelle
7
Zur Verdeutlichung gibt es also keinen Vorteil bei der Verwendung von [].forEach.call(['a','b'], cb)Over ['a','b'].forEach(cb)in alltäglichen Anwendungen mit Standard-Arrays, nur wenn versucht wird, über Array-ähnliche Strukturen zu iterieren, die keinen forEacheigenen Prototyp haben? Ist das korrekt?
Matt Fletcher
2
@ MattFletcher: Ja das stimmt. Beides wird funktionieren, aber warum überkomplizieren? Rufen Sie die Methode einfach direkt im Array selbst auf.
James Allardice
Cool, danke. Und ich weiß nicht warum, vielleicht nur aus Gründen der Straße, um alte Damen in ALDI und so zu beeindrucken. Ich bleibe bei [].forEach():)
Matt Fletcher
var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) funktioniert gut
daslicht
@daslicht nicht in älteren Versionen von IE! ( die jedoch forEachArrays unterstützen, aber keine NodeList-Objekte)
Djave
21

Die anderen Antworten haben diesen Code sehr gut erklärt, daher füge ich nur einen Vorschlag hinzu.

Dies ist ein gutes Beispiel für Code, der der Einfachheit und Klarheit halber überarbeitet werden sollte. Anstatt [].forEach.call()oder Array.prototype.forEach.call()jedes Mal, wenn Sie dies tun, eine einfache Funktion daraus zu machen:

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

Jetzt können Sie diese Funktion anstelle des komplizierteren und undurchsichtigeren Codes aufrufen:

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

Es kann besser mit geschrieben werden

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

});

Es wird document.querySelectorAll('a')ein Objekt zurückgegeben, das einem Array ähnlich ist, aber nicht vom ArrayTyp erbt . Wir rufen also die forEachMethode aus dem Array.prototypeObjekt mit dem Kontext als dem von zurückgegebenen Wert aufdocument.querySelectorAll('a')

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

Es ist im Grunde das gleiche wie:

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

Möchten Sie diese alte Frage aktualisieren:

Der Grund für [].foreach.call()das Durchlaufen von Elementen in modernen Browsern ist größtenteils vorbei. Wir können document.querySelectorAll("a").foreach()direkt verwenden.

NodeList-Objekte sind Sammlungen von Knoten, die normalerweise von Eigenschaften wie Node.childNodes und Methoden wie document.querySelectorAll () zurückgegeben werden.

Obwohl NodeList kein Array ist, ist es möglich, mit forEach () darüber zu iterieren . Es kann auch mit Array.from () in ein echtes Array konvertiert werden.

Einige ältere Browser haben jedoch weder NodeList.forEach () noch Array.from () implementiert. Dies kann mithilfe von Array.prototype.forEach () umgangen werden - siehe Beispiel dieses Dokuments.

František Heča
quelle
1

Ein leeres Array hat forEachin seinem Prototyp eine Eigenschaft, die ein Funktionsobjekt ist. (Das leere Array ist nur eine einfache Möglichkeit, einen Verweis auf die forEachFunktion aller ArrayObjekte zu erhalten.) Funktionsobjekte haben wiederum eine callEigenschaft, die auch eine Funktion ist. Wenn Sie die Funktion einer Funktion aufrufen call, wird die Funktion mit den angegebenen Argumenten ausgeführt. Das erste Argument wird thisin der aufgerufenen Funktion.

Dokumentation zur callFunktion finden Sie hier . Dokumentation für forEachist hier .

Ted Hopp
quelle
1

Fügen Sie einfach eine Zeile hinzu:

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

Und voila!

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

Genießen :-)

Warnung: NodeList ist eine globale Klasse. Verwenden Sie diese Empfehlung nicht, wenn Sie eine öffentliche Bibliothek schreiben. Es ist jedoch eine sehr bequeme Möglichkeit, die Selbstwirksamkeit zu steigern, wenn Sie an einer Website oder einer node.js-App arbeiten.

imos
quelle
1

Nur eine schnelle und schmutzige Lösung, die ich am Ende immer benutze. Ich würde Prototypen nicht anfassen, genauso gute Praxis. Natürlich gibt es viele Möglichkeiten, dies zu verbessern, aber Sie haben die Idee.

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
quelle
0

[]Gibt immer ein neues Array zurück. Dies entspricht einem Array, es wird new Array()jedoch garantiert zurückgegeben, da Arrayes vom Benutzer überschrieben werden []kann, während dies nicht möglich ist. Dies ist also ein sicherer Weg, um den Prototyp zu erhalten Array, der dann wie beschrieben callverwendet wird, um die Funktion auf der arraylike Knotenliste (this) auszuführen.

Ruft eine Funktion mit einem bestimmten Wert und Argumenten auf, die einzeln angegeben werden. mdn

Xotic750
quelle
Während []wird in der Tat immer ein Array zurückgeben, während window.Arraykann mit irgendetwas überschrieben werden (in allen alten Browsern), kann window.Array.prototype.forEachauch mit irgendetwas überschrieben werden. In beiden Fällen gibt es keine Garantie für die Sicherheit in Browsern, die keine Getter / Setter oder Versiegelung / Einfrieren von Objekten unterstützen (Einfrieren verursacht eigene Erweiterungsprobleme).
Norguard
Sie können die Antwort jederzeit bearbeiten, um zusätzliche Informationen zur Möglichkeit des Überschreibens der prototypeoder ihrer Methoden hinzuzufügen : forEach.
Xotic750
0

Norguard erklärte, WAS [].forEach.call() und James Allardice WARUM wir es tun: weil querySelectorAll eine zurückgibt NodeList, die keine forEach-Methode hat ...

Es sei denn, Sie haben einen modernen Browser wie Chrome 51+, Firefox 50+, Opera 38, Safari 10.

Wenn nicht, können Sie eine Polyfüllung hinzufügen :

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
quelle
0

Viele gute Informationen auf dieser Seite (siehe Antwort + Antwort + Kommentar ), aber ich hatte kürzlich die gleiche Frage wie das OP, und es dauerte einige Grabungen, um das ganze Bild zu erhalten. Hier ist eine kurze Version:

Das Ziel ist es, ArrayMethoden auf einem Array zu verwenden NodeList, das diese Methoden selbst nicht hat.

Ein älteres Muster hat die Methoden von Array über kooptiert Function.call()und ein Array-Literal ( []) verwendet, anstatt Array.prototypedass es kürzer zu tippen war:

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

Ein neueres Muster (nach ECMAScript 2015) ist zu verwenden Array.from():

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