Funktionen höherer Ordnung in JS: Young Fighter Course

Dieser Artikel richtet sich an Personen, die ihre ersten schüchternen Schritte auf dem heiklen Weg zum Erlernen von JavaScript unternehmen. Trotz der Tatsache, dass ich 2018 die ES5-Syntax verwende, damit der Artikel von jungen Padawanern verstanden werden kann, die den JavaScript Level 1-Kurs an der HTML Academy belegen.

Eines der Merkmale, das JS von vielen anderen Programmiersprachen unterscheidet, ist, dass in dieser Sprache eine Funktion ein „erstklassiges Objekt“ ist. Oder auf Russisch ist eine Funktion eine Bedeutung. Entspricht einer Zahl, einer Zeichenfolge oder einem Objekt. Wir können eine Funktion in eine Variable schreiben, sie in ein Array oder in eine Objekteigenschaft einfügen. Wir können sogar zwei Funktionen addieren. Tatsächlich wird daraus nichts Sinnvolles, aber tatsächlich - wir können!

function hello(){}; function world(){}; console.log(hello + world); //  ,  ,   //   ,     

Das Interessanteste ist, dass wir Funktionen erstellen können, die mit anderen Funktionen arbeiten - sie als Argumente akzeptieren oder als Wert zurückgeben. Solche Funktionen werden Funktionen höherer Ordnung genannt . Und heute werden wir, Mädchen und Jungen, darüber sprechen, wie wir diese Gelegenheit an die Bedürfnisse der Volkswirtschaft anpassen können. Unterwegs erfahren Sie mehr über einige nützliche Funktionen von Funktionen in JS.

Pipeline


Nehmen wir an, wir haben eine Sache, mit der Sie viele Stücke machen müssen. Angenommen, ein Benutzer hat eine Textdatei hochgeladen, in der Daten im JSON-Format gespeichert sind, und wir möchten deren Inhalt verarbeiten. Zuerst müssen wir die zusätzlichen Leerzeichen schneiden, die aufgrund von Benutzeraktionen oder des Betriebssystems an den Rändern "wachsen" können. Überprüfen Sie dann, ob der Text keinen Schadcode enthält (wer weiß, diese Benutzer). JSON.parse mit der Methode JSON.parse vom Text zum Objekt. Entfernen Sie dann die benötigten Daten von diesem Objekt. Und am Ende - senden Sie diese Daten an den Server. Sie bekommen so etwas:

 function trim(){/*  */}; function sanitize(){/*  */}; function parse(){/*  */}; function extractData(){/*  */}; function send(){/*  */}; var textFromFile = getTextFromFile(); send(extractData(parse(sanitize(trim(testFromFile)))); 

Es sieht so einverstanden aus. Außerdem haben Sie wahrscheinlich nicht bemerkt, dass eine schließende Klammer fehlt. Natürlich würde Ihnen die IDE dies mitteilen, aber es gibt immer noch ein Problem. Um dies zu lösen, wurde kürzlich ein neuer Operator |> vorgeschlagen. In der Tat ist es nicht neu, aber ehrlich aus funktionalen Sprachen entlehnt, aber das ist nicht der Punkt. Mit diesem Operator kann die letzte Zeile wie folgt umgeschrieben werden:

 textFromFile |> trim |> sanitize |> parse |> extractData |> send; 

Der Operator |> nimmt seinen linken Operanden und übergibt ihn als Argument an den rechten Operanden. Beispielsweise entspricht "Hello" |> console.log der console.log("Hello") . Dies ist gerade in Fällen sehr praktisch, in denen mehrere Funktionen entlang der Kette aufgerufen werden. Vor der Einführung dieses Operators wird jedoch viel Zeit vergehen (wenn dieser Vorschlag überhaupt akzeptiert wird), aber Sie müssen jetzt irgendwie leben. Daher können wir unserem Fahrrad eine Funktion schreiben, die dieses Verhalten simuliert:

 function pipe(){ var args = Array.from(arguments); var result = args.shift(); while(args.length){ var f = args.shift(); result = f(result); } return result; } pipe( textFromFile, trim, sanitize, parse, extractData, send ); 

Wenn Sie ein unerfahrener Javascriptist sind (Javascript? Javascript?), Kann Ihnen die erste Zeile der Funktion unverständlich erscheinen. Es ist ganz einfach: Innerhalb der Funktion verwenden wir das Schlüsselwort argument, um auf ein Array-ähnliches Objekt zuzugreifen, das alle an die Funktion übergebenen Argumente enthält. Dies ist sehr praktisch, wenn wir nicht im Voraus wissen, wie viele Argumente sie haben wird. Ein massives Objekt ist wie ein Array, aber nicht ganz. Daher konvertieren wir es mit der Array.from Methode in ein normales Array. Ich hoffe, der weitere Code ist bereits gut lesbar: Wir beginnen von links nach rechts, um Elemente aus dem Array zu extrahieren und sie auf die gleiche Weise wie der Operator |> aufeinander anzuwenden.

Protokollierung


Hier ist ein weiteres Beispiel, das dem wirklichen Leben nahe kommt. Angenommen, wir haben bereits eine Funktion f , die ... etwas Nützliches bewirkt. Und während wir unseren Code testen, möchten wir mehr darüber wissen, wie f es genau macht. Zu welchen Zeitpunkten wird aufgerufen, welche Argumente werden an ihn übergeben, welche Werte werden zurückgegeben.

Natürlich können wir mit jedem Funktionsaufruf Folgendes schreiben:

 var result = f(a, b); console.log(" f     " + a + "  " + b + "   " + result); console.log(" : " + Date.now()); 

Aber erstens ist es ziemlich umständlich. Und zweitens ist es sehr leicht, es zu vergessen. Eines Tages werden wir einfach f(a, b) schreiben, und seitdem wird sich die Dunkelheit der Unwissenheit in unseren Gedanken niederlassen. Es wird sich mit jeder neuen Herausforderung f , von der wir nichts wissen.

Idealerweise möchte ich, dass die Protokollierung automatisch erfolgt. Damit jedes Mal, wenn Sie f aufrufen, alle benötigten Dinge in die Konsole geschrieben werden. Und zum Glück haben wir einen Weg, dies zu tun. Lernen Sie die neue Funktion höherer Ordnung kennen!

 function addLogger(f){ return function(){ var args = Array.from(arguments); var result = f.apply(null, args); console.log(" " + f.name + "     " + args.join() + "    " + result + "\n" + " : " + Date.now()); return result; } } function sum(a, b){ return a + b; } var sumWithLogging = addLogger(sum); sum(1, 2); //   sumWithLogging(1, 2) //  

Eine Funktion übernimmt eine Funktion und gibt eine Funktion zurück, die die Funktion aufruft, die beim Erstellen der Funktion an die Funktion übergeben wurde. Entschuldigung, ich konnte nicht aufhören, dies zu schreiben. Jetzt auf Russisch: Die Funktion addLogger erstellt einen addLogger um die Funktion, die als Argument an sie übergeben wird. Das Einwickeln ist auch eine Funktion. Beim Aufruf sammelt es ein Array seiner Argumente auf die gleiche Weise wie im vorherigen Beispiel. Anschließend ruft es mit der Methode apply eine umschlossene Funktion mit denselben Argumenten auf und merkt sich das Ergebnis. Danach schreibt der Wrapper alles in die Konsole.

Hier haben wir den klassischen Man-in-the-Middle-Angriffsfall. Wenn Sie einen Wrapper anstelle von f verwenden, gibt es aus Sicht des Codes, der ihn verwendet, praktisch keinen Unterschied. Der Code kann davon ausgehen, dass er direkt mit f kommuniziert. In der Zwischenzeit meldet der Wrapper alles an Genosse Major.

Eins, zwei, drei, vier ...


Und noch eine Aufgabe in der Nähe der Praxis. Angenommen, wir müssen einige Entitäten nummerieren. Jedes Mal, wenn eine neue Entität angezeigt wird, erhalten wir eine neue Nummer, eine mehr als die vorherige. Dazu starten wir eine Funktion der folgenden Form:

 var lastNumber = 0; function getNewNumber(){ return lastNumber++; } 

Und dann haben wir eine neue Art von Einheit. Sagen wir, vorher haben wir die Hasen nummeriert, und jetzt gibt es auch Kaninchen. Wenn Sie eine Funktion sowohl für diese als auch für andere verwenden, macht jede an Kaninchen ausgegebene Nummer ein „Loch“ in der Reihe von Nummern, die an Hasen ausgegeben werden. Wir brauchen also die zweite Funktion und damit die zweite Variable:

 var lastHareNumber = 0; function getNewHareNumber(){ return lastHareNumber++; } var lastRabbitNumber = 0; function getNewRabbitNumber(){ return lastRabbitNumber++; } 

Sie haben das Gefühl, dass dieser Code schlecht riecht? Ich hätte gerne etwas Besseres. Erstens möchte ich in der Lage sein, solche Funktionen weniger ausführlich zu deklarieren, ohne den Code zu duplizieren. Und zweitens möchte ich die Variable, die die Funktion verwendet, irgendwie in die Funktion selbst "packen", um den Namespace nicht noch einmal zu verstopfen.

Und dann platzt ein Mann herein, der mit dem Konzept von OOP vertraut ist, und sagt:
"Grundstufe, Watson." Es ist notwendig, Zahlengeneratoren nicht zu Objekten, sondern zu Objekten zu machen. Objekte dienen nur zum Speichern von Funktionen, die mit Daten arbeiten, zusammen mit genau diesen Daten. Dann könnten wir so etwas schreiben wie:

 var numberGenerator = new NumberGenerator(); var n = numberGenerator.get(); 

Worauf ich antworten werde:
- Um ehrlich zu sein, stimme ich Ihnen voll und ganz zu. Und im Prinzip ist dies ein korrekterer Ansatz als das, was ich jetzt anbieten werde. Aber hier haben wir einen Artikel über Funktionen, nicht über OOP. Könntest du also eine Weile ruhig bleiben und mich fertig machen lassen?

Hier (Überraschung!) Die Funktion höherer Ordnung wird uns wieder helfen.

 function createNumberGenerator(){ var n = 0; return function(){ return n++; } } var getNewHareNumber = createNumberGenerator(); var getNewRabbitNumber = createNumberGenerator(); console.log( getNewHareNumber(), getNewHareNumber(), getNewHareNumber(), getNewRabbitNumber(), getNewRabbitNumber(), ); //    0, 1, 2, 0, 1 

Und hier haben einige Leute vielleicht eine Frage, vielleicht sogar in obszöner Form: Was zur Hölle ist los? Warum erstellen wir eine Variable, die nicht in der Funktion selbst verwendet wird? Wie greift eine interne Funktion darauf zu, wenn eine externe Funktion ihre Ausführung vor langer Zeit abgeschlossen hat? Warum erhalten zwei erstellte Funktionen, die sich auf dieselbe Variable beziehen, unterschiedliche Ergebnisse? Eine Antwort auf all diese Fragen ist der Abschluss .

Jedes Mal, createNumberGenerator Funktion createNumberGenerator , erstellt der JS-Interpreter ein magisches createNumberGenerator namens "Ausführungskontext". Grob gesagt ist dies ein solches Objekt, in dem alle in dieser Funktion deklarierten Variablen gespeichert sind. Wir können nicht als gewöhnliches Javascript-Objekt darauf zugreifen, aber es ist es trotzdem.

Wenn die Funktion „einfach“ war (z. B. Zahlen hinzufügen), erweist sich der Ausführungskontext nach Beendigung ihrer Arbeit als nutzlos. Wissen Sie, was mit unnötigen Daten in JS passiert? Sie werden von einem unersättlichen Dämon namens Garbage Collector verschlungen. Wenn die Funktion jedoch „kompliziert“ war, kann es vorkommen, dass jemand seinen Kontext auch nach Ausführung dieser Funktion noch benötigt. In diesem Fall schont ihn der Müllsammler, und er bleibt irgendwo in seiner Erinnerung hängen, damit diejenigen, die ihn brauchen, weiterhin Zugang zu ihm haben können.

Somit hat die von createNumberGenerator Funktion immer Zugriff auf eine eigene Kopie der Variablen n . Sie können sich das als Bag of Holding von D & D vorstellen. Sie stecken Ihre Hand in Ihre Tasche und befinden sich in einer persönlichen interdimensionalen „Tasche“, in der Sie alles aufbewahren können, was Sie wollen.

Entprellen


Es gibt so etwas wie "Bounce eliminieren". In diesem Fall möchten wir nicht, dass eine Funktion zu oft aufgerufen wird. Angenommen, es gibt eine bestimmte Schaltfläche, auf die geklickt wird, um einen „teuren“ Prozess zu starten (lange oder viel Speicher oder das Internet zu essen oder Jungfrauen zu opfern). Es kann vorkommen, dass ein ungeduldiger Benutzer mit einer Häufigkeit von mehr als zehn Hertz auf diese Schaltfläche klickt. Darüber hinaus hat der oben erwähnte Prozess eine solche Natur, dass es keinen Sinn macht, ihn zehnmal hintereinander auszuführen, da sich das Endergebnis nicht ändert. Dann wenden wir die „Chatter-Eliminierung“ an.

Das Wesentliche ist sehr einfach: Wir führen die Funktion nicht sofort, sondern nach einiger Zeit aus. Wenn die Funktion vor Ablauf dieser Zeit erneut aufgerufen wurde, setzen wir den Timer zurück. Somit kann der Benutzer mindestens tausendmal auf die Schaltfläche klicken - für das Opfer wird nur eine benötigt. Weniger Wörter, mehr Code:

 function debounce(f, delay){ var lastTimeout; return function(){ if(lastTimeout){ clearTimeout(lastTimeout); } var args = Array.from(arguments); lastTimeout = setTimeout(function(){ f.apply(null, args); }, delay); } } function sacrifice(name){ console.log(name + "     * *"); } function sacrificeDebounced = debounce(sacrifice, 500); sacrificeDebounced(""); sacrificeDebounced(""); sacrificeDebounced(""); 

In einer halben Sekunde wird Lena geopfert und Katya und Sveta werden dank unserer magischen Funktion überleben.

Wenn Sie die vorherigen Beispiele sorgfältig lesen, sollten Sie ein gutes Verständnis dafür haben, wie hier alles funktioniert. Die durch debounce erstellte Wrapper-Funktion löst die verzögerte Ausführung der ursprünglichen Funktion mit setTimeout aus . In diesem Fall wird die Timeout-ID in der Variablen lastTimeout gespeichert, auf die der Wrapper aufgrund des Schließens zugreifen kann. Befindet sich die Timeout- ID bereits in dieser Variablen, bricht der Wrapper dieses Timeout mit clearTimeout ab . Wenn das vorherige Timeout bereits abgeschlossen ist, geschieht nichts. Wenn nicht, umso schlimmer für ihn.

Damit werde ich vielleicht enden. Ich hoffe, dass Sie heute viele neue Dinge gelernt haben und vor allem - Sie haben alles verstanden, was Sie gelernt haben. Wir sehen uns wieder.

Source: https://habr.com/ru/post/de421537/


All Articles