So verwenden Sie Memoize, um JavaScript-Funktionsergebnisse zwischenzuspeichern und Ihren Code zu beschleunigen

Funktionen sind ein wesentlicher Bestandteil der Programmierung. Sie tragen dazu bei , unserem Code Modularität und Wiederverwendbarkeit zu verleihen .

Es ist durchaus üblich, unser Programm mithilfe von Funktionen, die wir später aufrufen können, um nützliche Aktionen auszuführen, in Blöcke zu unterteilen.

Manchmal kann es teuer werden, eine Funktion mehrmals aufzurufen (z. B. eine Funktion zur Berechnung der Fakultät einer Zahl). Aber es gibt eine Möglichkeit, solche Funktionen zu optimieren und sie viel schneller ausführen zu lassen: Caching .

Nehmen wir zum Beispiel an, wir müssen functiondie Fakultät einer Zahl zurückgeben:

function factorial(n) { // Calculations: n * (n-1) * (n-2) * ... (2) * (1) return factorial }

Großartig, jetzt lass uns finden factorial(50). Der Computer führt Berechnungen durch und gibt uns die endgültige Antwort zurück, Süße!

Wenn das erledigt ist, lass uns finden factorial(51). Der Computer führt erneut eine Reihe von Berechnungen durch und liefert uns das Ergebnis. Möglicherweise haben Sie jedoch bemerkt, dass wir bereits eine Reihe von Schritten wiederholen, die hätten vermieden werden können. Ein optimierter Weg wäre:

factorial(51) = factorial(50) * 51

Aber wir führen functiondie Berechnungen jedes Mal von Grund auf neu durch, wenn es heißt:

factorial(51) = 51 * 50 * 49 * ... * 2 * 1

Wäre es nicht cool, wenn unsere factorialFunktion sich irgendwie an die Werte aus ihren vorherigen Berechnungen erinnern und sie verwenden könnte, um die Ausführung zu beschleunigen?

In Memoization können wir functionuns die Ergebnisse merken (zwischenspeichern). Nachdem Sie nun ein grundlegendes Verständnis dafür haben, was wir erreichen möchten, finden Sie hier eine formale Definition:

Das Speichern ist eine Optimierungstechnik, die hauptsächlich verwendet wird, um Computerprogramme zu beschleunigen, indem die Ergebnisse teurer Funktionsaufrufe gespeichert und das zwischengespeicherte Ergebnis zurückgegeben werden, wenn dieselben Eingaben erneut auftreten

Das einfache Auswendiglernen bedeutet das Auswendiglernen oder Speichern im Speicher. Eine gespeicherte Funktion ist normalerweise schneller, denn wenn die Funktion anschließend mit den vorherigen Werten aufgerufen wird, wird anstelle der Ausführung der Funktion das Ergebnis aus dem Cache abgerufen.

So könnte eine einfache gespeicherte Funktion aussehen (und hier ist ein CodePen, falls Sie damit interagieren möchten) :

// a simple function to add something const add = (n) => (n + 10); add(9); // a simple memoized function to add something const memoizedAdd = () => { let cache = {}; return (n) => { if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = n + 10; cache[n] = result; return result; } } } // returned function from memoizedAdd const newAdd = memoizedAdd(); console.log(newAdd(9)); // calculated console.log(newAdd(9)); // cached

Memoization zum Mitnehmen

Einige Imbissbuden aus dem obigen Code sind:

  • memoizedAddgibt ein zurück, functiondas später aufgerufen wird. Dies ist möglich, weil Funktionen in JavaScript erstklassige Objekte sind, mit denen wir sie als Funktionen höherer Ordnung verwenden und eine andere Funktion zurückgeben können.
  • cachekann sich an seine Werte erinnern , da die zurückgegebene Funktion geschlossen ist.
  • Es ist wichtig, dass die gespeicherte Funktion rein ist. Eine reine Funktion gibt dieselbe Ausgabe für eine bestimmte Eingabe zurück, egal wie oft sie aufgerufen wird, wodurch die cacheArbeit wie erwartet ausgeführt wird.

Schreiben Sie Ihre eigene memoizeFunktion

Der vorherige Code funktioniert einwandfrei, aber was ist, wenn wir eine Funktion in eine gespeicherte Funktion verwandeln möchten?

So schreiben Sie Ihre eigene Memoize-Funktion (Codepen):

// a simple pure function to get a value adding 10 const add = (n) => (n + 10); console.log('Simple call', add(3)); // a simple memoize function that takes in a function // and returns a memoized function const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; // just taking one argument here if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = fn(n); cache[n] = result; return result; } } } // creating a memoized function for the 'add' pure function const memoizedAdd = memoize(add); console.log(memoizedAdd(3)); // calculated console.log(memoizedAdd(3)); // cached console.log(memoizedAdd(4)); // calculated console.log(memoizedAdd(4)); // cached

Das ist großartig! Diese einfache memoizeFunktion functionverpackt jedes einfache in ein gespeichertes Äquivalent. Der Code funktioniert gut für einfache Funktionen und kann leicht angepasst werden, um eine beliebige Anzahl von argumentsFunktionen gemäß Ihren Anforderungen zu verarbeiten. Eine andere Alternative besteht darin, einige De-facto-Bibliotheken zu verwenden, wie z.

  • Lodashs _.memoize(func, [resolver])
  • ES7 @memoizeDekorateure von Decko

Rekursive Funktionen auswendig lernen

Wenn Sie versuchen, eine rekursive Funktion an die memoizeobige Funktion oder _.memoizevon Lodash zu übergeben, sind die Ergebnisse nicht wie erwartet, da die rekursive Funktion bei ihren nachfolgenden Aufrufen sich selbst anstelle der gespeicherten Funktion aufruft und dabei die nicht verwendet cache.

Stellen Sie einfach sicher, dass Ihre rekursive Funktion die gespeicherte Funktion aufruft. So können Sie ein Fakultätsbeispiel für einen Lehrbuch (Codepen) optimieren:

// same memoize function from before const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; if (n in cache) { console.log('Fetching from cache', n); return cache[n]; } else { console.log('Calculating result', n); let result = fn(n); cache[n] = result; return result; } } } const factorial = memoize( (x) => { if (x === 0) { return 1; } else { return x * factorial(x - 1); } } ); console.log(factorial(5)); // calculated console.log(factorial(6)); // calculated for 6 and cached for 5

Einige Punkte aus diesem Code zu beachten:

  • Die factorialFunktion ruft rekursiv eine gespeicherte Version von sich selbst auf.
  • Die gespeicherte Funktion speichert die Werte früherer Fakultäten zwischen, wodurch die Berechnungen erheblich verbessert werden, da sie wiederverwendet werden können factorial(6) = 6 * factorial(5)

Ist Memoisierung dasselbe wie Caching?

Ja, irgendwie. Das Auswendiglernen ist eigentlich eine bestimmte Art des Zwischenspeicherns. Während sich das Zwischenspeichern im Allgemeinen auf jede Speichertechnik (wie das HTTP-Zwischenspeichern) für die zukünftige Verwendung beziehen kann, umfasst das Memoisieren speziell das Zwischenspeichern der Rückgabewerte von a function.

Wann Sie Ihre Funktionen auswendig lernen sollten

Obwohl es so aussieht, als ob Memoization mit allen Funktionen verwendet werden kann, gibt es tatsächlich begrenzte Anwendungsfälle:

  • Um eine Funktion zu speichern, sollte sie rein sein, damit die Rückgabewerte jedes Mal für dieselben Eingaben gleich sind
  • Das Speichern ist ein Kompromiss zwischen zusätzlichem Speicherplatz und zusätzlicher Geschwindigkeit und daher nur für Funktionen mit einem begrenzten Eingabebereich von Bedeutung, sodass zwischengespeicherte Werte häufiger verwendet werden können
  • Es könnte so aussehen, als sollten Sie sich Ihre API-Aufrufe merken, dies ist jedoch nicht erforderlich, da der Browser sie automatisch für Sie zwischenspeichert. Weitere Informationen finden Sie unter HTTP-Caching
  • Der beste Verwendung Fall , dass ich für memoized Funktionen gefunden ist für schwere Rechenfunktionen , die die Leistung erheblich verbessern können (factorial und Fibonacci sind nicht wirklich gut reale Welt Beispiele)
  • Wenn Sie sich für React / Redux interessieren, können Sie die Neuauswahl überprüfen, bei der mithilfe eines gespeicherten Selektors sichergestellt wird, dass Berechnungen nur durchgeführt werden, wenn eine Änderung in einem verwandten Teil des Statusbaums erfolgt.

Weiterführende Literatur

The following links can be useful if you would like to know more about some of the topics from this article in more detail:

  • Higher order functions in JavaScript
  • Closures in JavaScript
  • Pure functions
  • Lodash’s _.memoize docs and source code
  • More memoization examples here and here
  • reactjs/reselect

I hope this article was useful for you, and you’ve gained a better understanding of memoization in JavaScript :)

You may follow me on twitter for latest updates. I've also started posting more recent posts on my personal blog.