Was sie Ihnen nicht über die Array-Extras von ES5 erzählt haben

Jede neue Version von JavaScript fügt zusätzliche Extras hinzu, die die Programmierung erleichtern. EcmaScript 5 fügte einige dringend benötigte Methoden hinzu Array Datentyp, und während Sie Ressourcen finden, in denen Sie lernen, wie diese Methoden verwendet werden, wird normalerweise keine Diskussion über deren Verwendung mit einer anderen als einer langweiligen, benutzerdefinierten Funktion ausgeführt.

Alle Array-Extras ignorieren Löcher in Arrays.

Die in ES5 hinzugefügten neuen Array-Methoden werden normalerweise als bezeichnet Array-Extras. Sie erleichtern die Arbeit mit Arrays, indem sie Methoden zur Durchführung allgemeiner Operationen bereitstellen. Hier ist eine fast vollständige Liste der neuen Methoden:

  • Array.prototype.map
  • Array.prototype.reduce
  • Array.prototype.reduceRight
  • Array.prototype.filter
  • Array.prototype.forEach
  • Array.prototyp.jedes
  • Array.prototype.some

Array.prototype.indexOf und Array.prototype.lastIndexOf sind ebenfalls Teil dieser Liste, aber in diesem Tutorial werden nur die oben genannten sieben Methoden erörtert.


Was sie dir gesagt haben

Diese Methoden sind relativ einfach anzuwenden. Sie führen für jedes Element im Array eine Funktion aus, die Sie als erstes Argument angeben. In der Regel sollte die übergebene Funktion drei Parameter haben: das Element, den Index des Elements und das gesamte Array. Hier einige Beispiele:

[1, 2, 3] .map (Funktion (elem, index, arr) return elem * elem;); // return [1, 4, 9] [1, 2, 3, 4, 5] .filter (function (elem, index, arr) return elem% 2 === 0;); // Returns [2, 4] [1, 2, 3, 4, 5] .some (function (elem, index, arr) return elem> = 3;); // gibt true zurück [1, 2, 3, 4, 5] .every (function (elem, index, arr) return elem> = 3;); // gibt falsch zurück

Das reduzieren und reduziereRechts Methoden haben eine andere Parameterliste. Wie ihre Namen vermuten lassen, reduzieren sie ein Array auf einen einzelnen Wert. Der Anfangswert des Ergebnisses ist standardmäßig das erste Element im Array. Sie können jedoch ein zweites Argument an diese Methoden übergeben, um als Anfangswert zu dienen.

Die Rückruffunktion für diese Methoden akzeptiert vier Argumente. Der aktuelle Status ist das erste Argument und die verbleibenden Argumente sind das Element, der Index und das Array. Die folgenden Ausschnitte veranschaulichen die Verwendung dieser beiden Methoden:

[1, 2, 3, 4, 5] .reduce (Funktion (Summe, Element, Index, Arr) return sum + elem;); // gibt 15 zurück [1, 2, 3, 4, 5] .reduce (Funktion (Summe, Element, Index, Arr.) return sum + elem;, 10); // gibt 25 zurück

Aber das wussten Sie wahrscheinlich schon, oder? Gehen wir also auf etwas ein, mit dem Sie möglicherweise nicht vertraut sind.


Funktionale Programmierung zur Rettung

Es ist überraschend, dass mehr Leute das nicht wissen: Sie müssen keine neue Funktion erstellen und an sie übergeben .Karte() und Freunde. Darüber hinaus können Sie integrierte Funktionen wie z parseFloat es ist kein Wrapper erforderlich!

["1", "2", "3", "4"]. Map (parseFloat); // gibt zurück [1, 2, 3, 4]

Beachten Sie, dass einige Funktionen nicht wie erwartet funktionieren. Zum Beispiel, parseInt akzeptiert ein Radix als zweites Argument. Denken Sie nun daran, dass der Index des Elements als zweites Argument an die Funktion übergeben wird. Also, was wird das folgende wiederkommen??

["1", "2", "3", "4"]. Map (parseInt);

Genau: [1, NaN, NaN, NaN]. Zur Erklärung: Basis 0 wird ignoriert; Der erste Wert wird also wie erwartet analysiert. Die folgenden Basen enthalten nicht die als erstes Argument übergebene Zahl (z. B. enthält Basis 2 keine 3), was zu führt NaNs. Überprüfen Sie daher unbedingt das Mozilla Developer Network, bevor Sie eine Funktion verwenden, und Sie können loslegen.

Pro-Tipp: Sie können sogar eingebaute Konstruktoren als Argumente verwenden, da sie nicht mit aufgerufen werden müssen Neu. Als Ergebnis kann eine einfache Konvertierung in einen booleschen Wert durchgeführt werden Boolean, so was:

["yes", 0, "no", "", "true", "false"]. filter (Boolean); // gibt ["ja", "nein", "wahr", "falsch"] zurück

Ein paar andere schöne Funktionen sind encodeURIComponent, Date.Parse (Beachten Sie, dass Sie das nicht verwenden können Datum Der Konstruktor gibt immer das aktuelle Datum zurück, wenn er ohne aufgerufen wird Neu), Array.isArray und JSON.parse.


Vergiss es nicht .sich bewerben()

Die Verwendung integrierter Funktionen als Argumente für Array-Methoden kann zwar zu einer schönen Syntax führen, Sie sollten jedoch auch daran denken, dass Sie ein Array als zweites Argument von übergeben können Function.prototype.apply. Das ist praktisch, wenn Sie Methoden aufrufen, wie Math.max oder String.fromCharCode. Beide Funktionen akzeptieren eine variable Anzahl von Argumenten, daher müssen Sie sie in eine Funktion einschließen, wenn Sie die Array-Extras verwenden. Also statt:

var arr = [1, 2, 4, 5, 3]; var max = arr.reduce (Funktion (a, b) return Math.max (a, b););

Sie können folgendes schreiben:

var arr = [1, 2, 4, 5, 3]; var max = Math.max.apply (null, arr);

Dieser Code bietet auch einen schönen Leistungsvorteil. Als Randbemerkung: In EcmaScript 6 können Sie einfach schreiben:

var arr = [1, 2, 4, 5, 3]; var max = Math.max (… arr); // DIESES FUNKTIONIERT JETZT NICHT!

Lochlose Arrays

Alle Array-Extras ignorieren Löcher in Arrays. Ein Beispiel:

var a = ["hallo",,,,, "welt"]; // a [1] bis a [4] sind nicht definiert var count = a.reduce (function (count) return count + 1;, 0); console.log (Anzahl); // 2

Dieses Verhalten bringt wahrscheinlich einen Leistungsvorteil mit sich, aber in manchen Fällen kann es zu echten Schmerzen im Hintern kommen. Ein solches Beispiel könnte sein, wenn Sie ein Array von Zufallszahlen benötigen. Es ist nicht möglich, dies einfach zu schreiben:

var randomNums = new Array (5) .map (Math.random);

Denken Sie jedoch daran, dass Sie alle nativen Konstruktoren ohne aufrufen können Neu. Und noch ein nützlicher Leckerbissen: Function.prototype.apply Löcher ignoriert nicht. Zusammengefasst liefert dieser Code das korrekte Ergebnis:

var randomNums = Array.apply (null, neues Array (5)). map (Math.random);

Das unbekannte zweite Argument

Das meiste ist bekannt und wird von vielen Programmierern regelmäßig verwendet. Was die meisten nicht wissen (oder zumindest nicht verwenden), ist das zweite Argument der meisten Array - Extras (nur das reduzieren* Funktionen unterstützen es nicht).

Mit dem zweiten Argument können Sie eine übergeben diese Wert für die Funktion. Als Ergebnis können Sie verwenden Prototyp-Methoden. Das Filtern eines Arrays mit einem regulären Ausdruck wird beispielsweise zu einem Einzeiler:

["foo", "bar", "baz"]. filter (RegExp.prototype.test, / ^ b /); // gibt ["bar", "baz"] zurück

Auch die Prüfung, ob ein Objekt bestimmte Eigenschaften hat, wird zum Kinderspiel:

["foo", "isArray", "create"]. some (Object.prototype.hasOwnProperty, Object); // gibt true zurück (wegen Object.create)

Am Ende können Sie jede Methode verwenden, die Sie möchten:

// lass uns etwas verrückt machen [function (a) return a * a;  Funktion (b) return b * b * b; ] .map (Array.prototype.map, [1, 2, 3]); // gibt [[1, 4, 9], [1, 8, 27]] zurück

Dies wird bei der Verwendung verrückt Funktionsprotokoll.aufruf. Schau dir das an:

["foo", "\ n \ tbar", "\ r \ nbaz \ t"] .map (Function.prototype.call, String.prototype.trim); // gibt ["foo", "bar", "baz"] [true, 0, null, []]. map (Function.prototype.call, Object.prototype.toString); // gibt ["[object Boolean]", "[object Number]", "[object null]", "[object Array]"] zurück

Natürlich können Sie Ihren inneren Freak auch verwenden Funktionsprotokoll.aufruf als zweiter Parameter. Dabei wird jedes Element des Arrays mit seinem Index als erstem Argument und dem gesamten Array als zweitem aufgerufen:

[function (index, arr) // was auch immer Sie damit machen möchten]. forEach (Function.prototype.call, Function.prototype.call);

Lässt etwas Nützliches bauen

Mit allem, was gesagt wird, bauen wir einen einfachen Rechner. Wir möchten nur die Basisoperatoren unterstützen (+, -, *, /), und wir müssen die Bedienerprozedur einhalten. Also Multiplikation (*) und Abteilung (/) müssen vor der Zugabe bewertet werden (+) und Subtraktion (-).

Erstens definieren wir eine Funktion, die eine Zeichenfolge akzeptiert, die die Berechnung als erstes und einziges Argument darstellt.

Funktion berechnen (Berechnung) 

Im Funktionshauptteil konvertieren wir die Berechnung mithilfe eines regulären Ausdrucks in ein Array. Dann stellen wir sicher, dass wir die gesamte Berechnung analysiert haben, indem wir die Teile miteinander verbinden Array.prototype.join und Vergleichen des Ergebnisses mit der ursprünglichen Berechnung.

var parts = berechnung.match (// ziffern | operatoren | whitespace / (?:\-?[\d\.(++||| if (Berechnung! == parts.join ("")) neuen Fehler werfen ("Berechnung konnte nicht analysiert werden")

Danach rufen wir an String.prototype.trim für jedes Element, um Leerraum zu beseitigen. Dann filtern wir das Array und entfernen Falsey-Elemente (z. B. f leere Zeichenfolgen)..

parts = parts.map (Function.prototype.call, String.prototype.trim); parts = parts.filter (Boolean);

Jetzt erstellen wir ein separates Array, das geparste Zahlen enthält.

var nums = parts.map (parseFloat);

Sie können eingebaute Funktionen wie zB übergeben parseFloat es ist kein Wrapper erforderlich!

An diesem Punkt ist der einfachste Weg, um fortzufahren, ein einfacher zum-Schleife. Darin bauen wir ein anderes Array (genannt verarbeitet) mit bereits angewendeter Multiplikation und Division. Die Grundidee ist, jede Operation auf eine Addition zu reduzieren, so dass der letzte Schritt ziemlich trivial wird.

Wir prüfen jedes Element der nums Array, um sicherzustellen, dass es nicht ist NaN; Wenn es keine Zahl ist, dann ist es ein Operator. Der einfachste Weg, dies zu tun, besteht darin, die Tatsache in JavaScript zu nutzen, NaN! == NaN. Wenn wir eine Zahl finden, fügen wir sie dem Ergebnisfeld hinzu. Wenn wir einen Operator finden, wenden wir ihn an. Wir überspringen Additionsoperationen und ändern nur das Vorzeichen der nächsten Zahl für die Subtraktion.

Multiplikation und Division müssen anhand der beiden umgebenden Zahlen berechnet werden. Da wir die vorherige Nummer bereits an das Array angehängt haben, muss sie mit entfernt werden Array.prototype.pop. Das Ergebnis der Berechnung wird an das Ergebnisfeld angehängt und kann nun hinzugefügt werden.

var verarbeitet = []; für (var i = 0; i < parts.length; i++) if( nums[i] === nums[i] ) processed.push( nums[i] );  else  switch( parts[i] )  case "+": continue; //ignore case "-": processed.push(nums[++i] * -1); break; case "*": processed.push(processed.pop() * nums[++i]); break; case "/": processed.push(processed.pop() / nums[++i]); break; default: throw new Error("unknown operation: " + parts[i]);   

Der letzte Schritt ist ziemlich einfach: Wir addieren einfach alle Zahlen und geben unser Endergebnis zurück.

return processing.reduce (function (result, elem) return result + elem;);

Die abgeschlossene Funktion sollte so aussehen:

Funktion berechnen (Berechnung) // Ein Array mit den einzelnen Teilen erstellen var parts = berechnung.match (// Ziffern | Operatoren | Leerzeichen / (?: \ -? [\ d \.] +) | [- \ + \ * \ /] | \ s + / g); // Test, ob alles übereinstimmt, wenn (Berechnung! == parts.join ("")) throw new Fehler ("Berechnung konnte nicht analysiert werden") // alle Whitespace-Parts = parts.map (Function.prototype) entfernen. call, String.prototype.trim); parts = parts.filter (Boolean); // ein separates Array mit geparsten Zahlen erstellen var nums = parts.map (parseFloat); // ein anderes Array erstellen, bei dem alle Operationen auf Zusätze reduziert wurden var verarbeitete = []; für (var i = 0; i < parts.length; i++) if( nums[i] === nums[i] ) //nums[i] isn't NaN processed.push( nums[i] );  else  switch( parts[i] )  case "+": continue; //ignore case "-": processed.push(nums[++i] * -1); break; case "*": processed.push(processed.pop() * nums[++i]); break; case "/": processed.push(processed.pop() / nums[++i]); break; default: throw new Error("unknown operation: " + parts[i]);    //add all numbers and return the result return processed.reduce(function(result, elem) return result + elem; ); 

Okay, lass es uns testen:

berechne ("2 + 2,5 * 2") // gibt zurück 7 berechnet ("12/6 + 4 * 3") // gibt 14 zurück

Es scheint zu funktionieren! Es gibt immer noch einige Randfälle, die nicht behandelt werden, z. B. Berechnungen mit dem Operator-First oder Zahlen, die mehrere Punkte enthalten. Unterstützung für Klammern wäre schön, aber in diesem einfachen Beispiel machen wir uns keine Sorgen mehr.


Einpacken

Während die Array-Extras von ES5 auf den ersten Blick recht trivial erscheinen mögen, verraten sie etwas Tiefe, wenn Sie ihnen eine Chance geben. Plötzlich wird funktionale Programmierung in JavaScript mehr als Callback-Höllen- und Spaghetti-Code. Das zu realisieren, war für mich ein wahrer Augenöffner und beeinflusste meine Art, Programme zu schreiben.

Wie oben gezeigt, gibt es natürlich immer Fälle, in denen Sie stattdessen eine reguläre Schleife verwenden möchten. Aber das ist der schöne Teil, den Sie nicht brauchen.