Funktionales JavaScript: Was sind Funktionen höherer Ordnung und warum werden sie benötigt?


"Funktionen höherer Ordnung" ist eine dieser Phrasen, die oft verstreut sind. Aber selten kann jemand anhalten und erklären, was es ist. Möglicherweise kennen Sie bereits Funktionen höherer Ordnung. Aber wie setzen wir sie in realen Projekten ein? Wann und warum sind sie hilfreich? Können wir das DOM mit ihrer Hilfe manipulieren? Oder zeigen sich Leute, die diese Funktionen nutzen, nur? Vielleicht komplizieren sie den Code sinnlos?

Früher dachte ich, dass Funktionen höherer Ordnung nützlich sind. Jetzt halte ich sie für die wichtigste Eigenschaft von JavaScript als Sprache. Bevor wir dies diskutieren, wollen wir zunächst herausfinden, was genau Funktionen höherer Ordnung sind. Und wir werden mit Funktionen als Variablen beginnen.

Funktioniert als erstklassige Objekte


In JavaScript gibt es mindestens drei Möglichkeiten (es gibt insgesamt mehr), um eine neue Funktion zu schreiben. Zunächst können Sie eine Funktionsdeklaration schreiben:

// Take a DOM element and wrap it in a list item element. function itemise(el) { const li = document.createElement('li'); li.appendChild(el); return li; } 

Ich hoffe du verstehst alles. Außerdem wissen Sie wahrscheinlich, dass Sie einen Funktionsausdruck schreiben können :

 const itemise = function(el) { const li = document.createElement('li'); li.appendChild(el); return li; } 

Und schließlich gibt es eine andere Möglichkeit, dieselbe Funktion zu schreiben - als Pfeilfunktion :

 const itemise = (el) => { const li = document.createElement('li'); li.appendChild(el); return li; } 

In diesem Fall sind alle drei Methoden gleichwertig. Obwohl dies nicht immer der Fall ist, weist jede Methode in der Praxis kleine Unterschiede auf, die mit der Magie eines bestimmten Schlüsselworts und von Beschriftungen in Stapelspuren zusammenhängen.

Beachten Sie jedoch, dass die letzten beiden Beispiele die Funktion einer Variablen zuweisen. Es sieht aus wie eine Kleinigkeit. Warum nicht einer Variablen eine Funktion zuweisen? Aber es ist sehr wichtig. Funktionen in JavaScript gehören zur " ersten Klasse ". Deshalb können wir:

  • Weisen Sie Variablen Funktionen zu.
  • Übergeben Sie Funktionen als Argumente an andere Funktionen.
  • Funktionen von anderen Funktionen zurückgeben.

Das ist wunderbar, aber was hat das alles mit Funktionen höherer Ordnung zu tun? Achten Sie auf die letzten beiden Punkte. Bald werden wir zu ihnen zurückkehren, aber jetzt schauen wir uns einige Beispiele an.

Wir haben die Zuordnung von Funktionen zu Variablen gesehen. Was ist mit der Übergabe als Parameter? Schreiben wir eine Funktion, die mit DOM-Elementen verwendet werden kann. Wenn wir document.querySelectorAll() ausführen, erhalten wir im Gegenzug kein Array, sondern eine NodeList . NodeList hat keine .map() -Methode wie Arrays, daher schreiben wir .map() :

 // Apply a given function to every item in a NodeList and return an array. function elListMap(transform, list) { // list might be a NodeList, which doesn't have .map(), so we convert // it to an array. return [...list].map(transform); } // Grab all the spans on the page with the class 'for-listing'. const mySpans = document.querySelectorAll('span.for-listing'); // Wrap each one inside an <li> element. We re-use the // itemise() function from earlier. const wrappedList = elListMap(itemise, mySpans); 

Hier übergeben wir die itemise-Funktion als Argument an die elListMap Funktion. Mit elListMap können wir jedoch nicht nur Listen erstellen. Mit seiner Hilfe können Sie beispielsweise einer Reihe von Elementen eine Klasse hinzufügen:

 function addSpinnerClass(el) { el.classList.add('spinner'); return el; } // Find all the buttons with class 'loader' const loadButtons = document.querySelectorAll('button.loader'); // Add the spinner class to all the buttons we found. elListMap(addSpinnerClass, loadButtons); 

elLlistMap übernimmt eine andere Funktion als Parameter und konvertiert. Das heißt, wir können elListMap , um verschiedene Probleme zu lösen.

Wir haben uns ein Beispiel für die Übergabe von Funktionen als Parameter angesehen. Lassen Sie uns nun über die Rückgabe einer Funktion von einer Funktion sprechen. Wie sieht es aus?

Zuerst schreiben wir die übliche alte Funktion. Wir müssen eine Liste der li Elemente nehmen und in ul einwickeln. Einfach:

 function wrapWithUl(children) { const ul = document.createElement('ul'); return [...children].reduce((listEl, child) => { listEl.appendChild(child); return listEl; }, ul); } 

Und wenn wir dann eine Reihe von Absatzelementen haben, die wir in ein div möchten? Kein Problem, wir werden eine weitere Funktion dafür schreiben:

 function wrapWithDiv(children) { const div = document.createElement('div'); return [...children].reduce((divEl, child) => { divEl.appendChild(child); return divEl; }, div); } 

Es funktioniert großartig. Diese beiden Funktionen sind jedoch sehr ähnlich. Der einzige Unterschied besteht in dem übergeordneten Element, das wir erstellt haben.

Jetzt könnten wir eine Funktion schreiben, die zwei Parameter akzeptiert: den Typ des übergeordneten Elements und die Liste der untergeordneten Elemente. Es gibt aber noch eine andere Option. Wir können eine Funktion erstellen, die eine Funktion zurückgibt. Zum Beispiel:

 function createListWrapperFunction(elementType) { // Straight away, we return a function. return function wrap(children) { // Inside our wrap function, we can 'see' the elementType parameter. const parent = document.createElement(elementType); return [...children].reduce((parentEl, child) => { parentEl.appendChild(child); return parentEl; }, parent); } } 

Es könnte auf den ersten Blick etwas kompliziert aussehen, also teilen wir den Code auf. Wir haben eine Funktion erstellt, die nur eine andere Funktion zurückgibt. Diese Rückgabefunktion merkt sich jedoch den Parameter elementType . Wenn wir dann die zurückgegebene Funktion aufrufen, weiß sie bereits, welches Element erstellt werden soll. Daher können Sie wrapWithUl und wrapWithDiv erstellen:

 const wrapWithUl = createListWrapperFunction('ul'); // Our wrapWithUl() function now 'remembers' that it creates a ul element. const wrapWithDiv = createListWreapperFunction('div'); // Our wrapWithDiv() function now 'remembers' that it creates a div element. 

Dieser Trick wird als Abschluss bezeichnet, wenn sich die zurückgegebene Funktion an etwas „erinnert“. Mehr darüber können Sie hier lesen. Verschlüsse sind unglaublich praktisch, aber im Moment werden wir nicht darüber nachdenken.

Also haben wir aussortiert:

  • Zuweisen einer Funktion zu einer Variablen.
  • Übergabe einer Funktion als Parameter.
  • Rückgabe einer Funktion von einer anderen Funktion ...

Im Allgemeinen sind die Funktionen der ersten Klasse eine angenehme Sache. Aber was hat die Funktion höherer Ordnung damit zu tun? Schauen wir uns die Definition an.

Was ist eine Funktion höherer Ordnung?


Definition : Dies ist eine Funktion, die eine Funktion als Argument verwendet oder als Ergebnis eine Funktion zurückgibt.

Ist das bekannt? In JavaScript sind dies erstklassige Funktionen. Das heißt, "Funktionen höherer Ordnung" haben genau die gleichen Vorteile. Mit anderen Worten, es ist nur ein phantasievoller Name für eine einfache Idee.

Funktionsbeispiele höherer Ordnung


Wenn Sie anfangen zu suchen, bemerken Sie überall Funktionen höherer Ordnung. Am häufigsten sind Funktionen, die andere Funktionen als Parameter verwenden.

Funktionen, die andere Funktionen als Parameter übernehmen


Wenn Sie einen Rückruf übergeben, verwenden Sie eine Funktion höherer Ordnung. In der Front-End-Entwicklung sind sie überall zu finden. Eine der häufigsten ist die .addEventListener() -Methode. Wir verwenden es, wenn wir als Reaktion auf bestimmte Ereignisse Aktionen ausführen möchten. Zum Beispiel möchte ich eine Schaltfläche erstellen, die eine Warnung anzeigt:

 function showAlert() { alert('Fallacies do not cease to be fallacies because they become fashions'); } document.body.innerHTML += `<button type="button" class="js-alertbtn"> Show alert </button>`; const btn = document.querySelector('.js-alertbtn'); btn.addEventListener('click', showAlert); 

Hier haben wir eine Funktion erstellt, die eine Warnung anzeigt, der Seite eine Schaltfläche hinzugefügt und die Funktion showAlert() als Argument an btn.addEventListener() .

Wir stoßen auch auf Funktionen höherer Ordnung, wenn wir Array-Iterationsmethoden verwenden : zum Beispiel .map() , .filter() und .reduce() . Wie in der Funktion elListMap() :

 function elListMap(transform, list) { return [...list].map(transform); } 

Funktionen höherer Ordnung helfen auch dabei, mit Verzögerungen und Timing zu arbeiten. Die Funktionen setTimeout() und setInterval() helfen bei der Steuerung, wann Funktionen ausgeführt werden. Wenn Sie beispielsweise die highlight nach 30 Sekunden entfernen müssen, können Sie dies folgendermaßen tun:

 function removeHighlights() { const highlightedElements = document.querySelectorAll('.highlighted'); elListMap(el => el.classList.remove('highlighted'), highlightedElements); } setTimeout(removeHighlights, 30000); 

Wieder haben wir eine Funktion erstellt und als Argument an eine andere Funktion übergeben.

Wie Sie sehen können, verfügt JavaScript häufig über Funktionen, die andere Funktionen akzeptieren. Und Sie verwenden sie wahrscheinlich bereits.

Funktionen Rückgabe von Funktionen


Funktionen dieser Art werden nicht so oft gefunden wie die vorherigen. Sie sind aber auch nützlich. Eines der besten Beispiele ist die Funktion Maybe () . Ich habe eine Variante aus dem Allongé JavaScript-Buch angepasst:

 function maybe(fn) return function _maybe(...args) { // Note that the == is deliberate. if ((args.length === 0) || args.some(a => (a == null)) { return undefined; } return fn.apply(this, args); } } 

Anstatt den Code zu verstehen, wollen wir zunächst sehen, wie er angewendet werden kann. Schauen wir uns noch einmal die Funktion elListMap() :

 // Apply a given function to every item in a NodeList and return an array. function elListMap(transform, list) { // list might be a NodeList, which doesn't have .map(), so we convert // it to an array. return [...list].map(transform); } 

Was passiert, wenn ich versehentlich null oder einen undefinierten Wert an elListMap() ? Wir werden einen TypeError und einen Fall der aktuellen Operation erhalten, was auch immer es sein mag. Dies kann mit der Funktion maybe() vermieden werden:

 const safeElListMap = maybe(elListMap); safeElListMap(x => x, null); // ← undefined 

Anstatt zu fallen, gibt die Funktion undefined . Und wenn wir dies an eine andere Funktion übergeben würden, die durch maybe() geschützt ist, würden wir wieder undefined . maybe() eine beliebige Anzahl von Funktionen schützen. Es ist viel einfacher, eine Milliarde if zu schreiben.

Funktionen, die Funktionen zurückgeben, sind auch in der React-Welt üblich. Zum Beispiel connect() .

Und was dann?


Wir haben mehrere Beispiele für die Verwendung von Funktionen höherer Ordnung gesehen. Und was dann? Was können sie uns geben, was wir ohne sie nicht bekommen können?

Um diese Frage zu beantworten, schauen wir uns ein anderes Beispiel an - die integrierte Array-Methode .sort() . Ja, er hat Fehler. Es ändert das Array, anstatt ein neues zurückzugeben. Aber vergessen wir es jetzt. Die .sort() -Methode ist eine Funktion höherer Ordnung, die eine andere Funktion als einen der Parameter verwendet.

Wie funktioniert es Wenn wir ein Array von Zahlen sortieren möchten, müssen wir zuerst eine Vergleichsfunktion erstellen:

 function compareNumbers(a, b) { if (a === b) return 0; if (a > b) return 1; /* else */ return -1; } 

Sortieren Sie nun das Array:

 let nums = [7, 3, 1, 5, 8, 9, 6, 4, 2]; nums.sort(compareNumbers); console.log(nums); // 〕[1, 2, 3, 4, 5, 6, 7, 8, 9] 

Sie können Nummernlisten sortieren. Aber was nützt es? Wie oft müssen wir eine Liste mit Nummern sortieren? Nicht oft. Normalerweise muss ich eine Reihe von Objekten sortieren:

 let typeaheadMatches = [ { keyword: 'bogey', weight: 0.25, matchedChars: ['bog'], }, { keyword: 'bog', weight: 0.5, matchedChars: ['bog'], }, { keyword: 'boggle', weight: 0.3, matchedChars: ['bog'], }, { keyword: 'bogey', weight: 0.25, matchedChars: ['bog'], }, { keyword: 'toboggan', weight: 0.15, matchedChars: ['bog'], }, { keyword: 'bag', weight: 0.1, matchedChars: ['b', 'g'], } ]; 

Angenommen, ich möchte dieses Array nach dem Gewicht jedes Datensatzes sortieren. Ich könnte eine neue Sortierfunktion von Grund auf neu schreiben. Aber warum, wenn Sie eine neue Vergleichsfunktion erstellen können:

 function compareTypeaheadResult(word1, word2) { return -1 * compareNumbers(word1.weight, word2.weight); } typeaheadMatches.sort(compareTypeaheadResult); console.log(typeaheadMatches); // 〕[{keyword: "bog", weight: 0.5, matchedChars: ["bog"]}, … ] 

Sie können eine Vergleichsfunktion für jede Art von Array schreiben. Die .sort() -Methode hilft uns: „Wenn Sie mir eine Vergleichsfunktion geben, sortiere ich jedes Array. Mach dir keine Sorgen über den Inhalt. Wenn Sie eine Sortierfunktion angeben, werde ich sie sortieren. “ Daher müssen wir keinen eigenen Sortieralgorithmus schreiben, sondern konzentrieren uns auf die viel einfachere Aufgabe, zwei Elemente zu vergleichen.

Stellen Sie sich nun vor, wir verwenden keine Funktionen höherer Ordnung. Wir können keine Funktion an die .sort() -Methode übergeben. Wir müssen jedes Mal eine neue Sortierfunktion schreiben, wenn wir ein Array einer anderen Art sortieren müssen. Oder Sie müssen dasselbe mit Funktionszeigern oder Objekten neu erfinden. In jedem Fall wird es sehr unangenehm ausfallen.

Wir haben jedoch Funktionen höherer Ordnung, mit denen wir die Sortierfunktion von der Vergleichsfunktion trennen können. .sort() ein Entwickler eines intelligenten Browsers hat .sort() aktualisiert, um einen schnelleren Algorithmus zu verwenden. Dann gewinnt Ihr Code nur, unabhängig davon, was sich in den sortierbaren Arrays befindet. Und dieses Schema gilt für eine ganze Reihe von Funktionen von Arrays höherer Ordnung .

Dies führt uns zu einer solchen Idee. Die Methode .sort() abstrahiert die Sortieraufgabe vom Inhalt des Arrays. Dies wird als Trennung von Bedenken bezeichnet. Mit Funktionen höherer Ordnung können Sie Abstraktionen erstellen, die ohne sie sehr umständlich oder sogar unmöglich wären. Und die Erstellung von Abstraktionen macht 80% der Arbeit von Softwareentwicklern aus.

Wenn wir den Code umgestalten, um Wiederholungen zu entfernen, erstellen wir Abstraktionen. Wir sehen das Muster und ersetzen es durch eine abstrakte Darstellung. Dadurch wird der Code aussagekräftiger und verständlicher. Zumindest ist das das Ziel.

Funktionen höherer Ordnung sind ein leistungsstarkes Werkzeug zum Erstellen von Abstraktionen. Und mit Abstraktionen ist ein ganzer Zweig der Mathematik, die Kategorietheorie, verbunden. Genauer gesagt widmet sich die Kategorietheorie der Suche nach Abstraktionen von Abstraktionen. Mit anderen Worten, wir sprechen über das Finden von Mustern von Mustern. Und in den letzten 70 Jahren haben clevere Programmierer viele Ideen von dort ausgeliehen, die sich in die Eigenschaften von Sprachen und Bibliotheken verwandelt haben. Wenn wir diese Muster lernen, können wir manchmal große Codeteile ersetzen. Oder vereinfachen Sie komplexe Probleme zu eleganten Kombinationen einfacher Bausteine. Diese Blöcke sind Funktionen höherer Ordnung. Daher sind sie so wichtig, dass sie uns ein leistungsstarkes Werkzeug bieten, um die Komplexität unseres Codes zu bekämpfen.

Zusätzliche Materialien zu Funktionen höherer Ordnung:


Sie verwenden wahrscheinlich bereits Funktionen höherer Ordnung. Dies ist in JavaScript so einfach, dass wir nicht einmal darüber nachdenken. Aber es ist besser zu wissen, worüber die Leute sprechen, wenn sie diesen Satz sagen. Das ist nicht schwer. Aber hinter einer einfachen Idee steckt viel Kraft.

Wenn Sie Erfahrung in der funktionalen Programmierung haben, werden Sie vielleicht feststellen, dass ich keine reinen Funktionen und einige ... ausführliche Funktionsnamen verwendet habe. Dies liegt nicht daran, dass ich nichts über unreine Funktionen oder die allgemeinen Prinzipien der funktionalen Programmierung gehört habe. Und ich schreibe keinen solchen Code in der Produktion. Ich habe versucht, praktische Beispiele zu finden, die Anfängern klar sind. Manchmal musste ich Kompromisse eingehen. Bei Interesse habe ich bereits über funktionale Sauberkeit und die allgemeinen Prinzipien der funktionalen Programmierung geschrieben .

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


All Articles