
Hast du schon von memoization
? Es ist übrigens eine super einfache Sache - merken Sie sich einfach, welches Ergebnis Sie von einem ersten Funktionsaufruf erhalten haben, und verwenden Sie es, anstatt es das zweite Mal aufzurufen - rufen Sie keine echten Dinge ohne Grund auf, verschwenden Sie keine Zeit .
Das Überspringen einiger intensiver Operationen ist eine sehr verbreitete Optimierungstechnik. Jedes Mal, wenn Sie etwas nicht tun - tun Sie es nicht. Versuchen Sie, den Cache zu verwenden - memcache
, file cache
, local cache
- jeden Cache! Ein Muss für Backend-Systeme und ein wesentlicher Bestandteil jedes Backend-Systems von Vergangenheit und Gegenwart.

Memoization vs Caching
Das Auswendiglernen ist wie das Zwischenspeichern. Nur ein bisschen anders. Kein Cache, nennen wir es Kashe.
Lange Rede, kurzer Sinn, aber das Auswendiglernen ist kein Cache, kein dauerhafter Cache. Es kann auf einer Serverseite sein, kann es aber nicht und sollte kein Cache auf einer Clientseite sein. Es geht mehr um verfügbare Ressourcen, Nutzungsmuster und die Gründe für die Verwendung.
Problem - Cache benötigt einen "Cache-Schlüssel"
Der Cache speichert und ruft Daten mithilfe eines String- Cache- key
. Es ist bereits ein Problem, einen eindeutigen und verwendbaren Schlüssel zu erstellen, aber dann müssen Sie Daten serialisieren und de-serialisieren, um sie erneut auf einem stringbasierten Medium zu speichern ... kurz gesagt - der Cache ist möglicherweise nicht so schnell, wie Sie vielleicht denken. Besonders verteilter Cache.
Memoization benötigt keinen Cache-Schlüssel
Gleichzeitig wird kein Schlüssel zum Auswendiglernen benötigt. Normalerweise * verwendet es Argumente wie sie sind, versucht nicht, einen einzelnen Schlüssel daraus zu erstellen, und verwendet kein global verfügbares freigegebenes Objekt zum Speichern von Ergebnissen, wie dies normalerweise im Cache der Fall ist.
Der Unterschied zwischen Memoization und Cache liegt in API INTERFACE !
Normalerweise bedeutet * nicht immer. Lodash.memoize verwendet standardmäßig JSON.stringify
, um übergebene Argumente in einen String-Cache zu konvertieren (gibt es eine andere Möglichkeit? Nein!). Nur weil sie diesen Schlüssel verwenden, um auf ein internes Objekt zuzugreifen, das einen zwischengespeicherten Wert enthält. Fast-Memoize , "die schnellstmögliche Memo-Bibliothek", macht dasselbe. Beide benannten Bibliotheken sind keine Memoization-Bibliotheken, sondern Cache-Bibliotheken.
Es ist erwähnenswert, dass JSON.stringify möglicherweise zehnmal langsamer ist als eine Funktion.
Offensichtlich besteht die einfache Lösung für das Problem darin, KEINEN Cache-Schlüssel zu verwenden und NICHT mit diesem Schlüssel auf einen internen Cache zuzugreifen. Denken Sie also an die letzten Argumente, mit denen Sie aufgerufen wurden. Wie memoizerific oder reselect tun.
Memoizerific ist wahrscheinlich die einzige allgemeine Caching-Bibliothek, die Sie verwenden möchten.
Die Cache-Größe
Der zweite große Unterschied zwischen allen Bibliotheken besteht in der Cache-Größe und der Cache-Struktur.
Haben Sie jemals darüber nachgedacht - warum erneut reselect
oder memoize-one
enthält nur ein letztes Ergebnis? Nicht "nicht den Cache-Schlüssel verwenden, um mehr als ein Ergebnis speichern zu können" , sondern weil es keinen Grund gibt, mehr als nur ein letztes Ergebnis zu speichern .
... Es geht mehr um:
- verfügbare Ressourcen - Eine einzelne Cache-Zeile ist sehr ressourcenschonend
- Nutzungsmuster - sich an etwas "an Ort und Stelle" zu erinnern, ist ein gutes Muster. "An Ort und Stelle" benötigen Sie normalerweise nur ein letztes Ergebnis.
- Der Grund für die Verwendung von Modularität, Isolation und Speichersicherheit sind gute Gründe. Wenn Sie den Cache nicht mit dem Rest Ihrer Anwendung teilen, ist dies in Bezug auf Cache-Kollisionen nur sicherer.
Ein einziges Ergebnis ?!
Ja - das einzige Ergebnis. Mit einem Ergebnis, das einige klassische Dinge auswendig gelernt hat, wie die Erzeugung von auswendig gelernten Fibonacci-Zahlen (die Sie möglicherweise in jedem Artikel über das Auswendiglernen als Beispiel finden ), wäre dies nicht möglich . Aber normalerweise machen Sie etwas anderes - wer braucht einen Fibonacci am Frontend? Im Backend? Beispiele aus der Praxis sind weit entfernt von abstrakten IT-Tests .
Dennoch gibt es zwei GROSSE Probleme bei einer einwertigen Memoisierungsart.
Problem 1 - es ist "zerbrechlich"
Standardmäßig sollten alle Argumente übereinstimmen, genau das "===" sein. Wenn ein Argument nicht übereinstimmt, ist das Spiel beendet. Auch wenn dies von der Idee des Auswendiglernen herrührt - das ist heutzutage vielleicht nicht mehr das, was Sie wollen. Ich meine - Sie möchten so viel wie möglich und so oft wie möglich auswendig lernen.
Sogar ein Cache-Miss ist ein Cache-Lösch-Headshot.
Es gibt einen kleinen Unterschied zwischen "heutzutage" und "gestern" - unveränderlichen Datenstrukturen, die beispielsweise in Redux verwendet werden.
const getSomeDataFromState = memoize(state => compute(state.tasks));
Sieht gut aus? Siehst du richtig aus? Der Status kann sich jedoch ändern, wenn Aufgaben nicht ausgeführt wurden und Sie nur übereinstimmende Aufgaben benötigen.
Strukturelle Selektoren sind hier, um den Tag mit ihrem stärksten Krieger - Reselect - zu retten. Reselect ist nicht nur eine Memoisierungsbibliothek, sondern die Leistung kommt auch von Memoisierungskaskaden oder Objektiven (was sie nicht sind, aber Selektoren als optische Objektive betrachten).
Infolgedessen müssen Sie bei unveränderlichen Daten immer zuerst auf das Datenelement "fokussieren", das Sie wirklich benötigen, und dann Berechnungen durchführen, da sonst der Cache abgelehnt wird und alle Ideen hinter dem Auswendiglernen verschwinden.
Dies ist tatsächlich ein großes Problem, insbesondere für Neulinge, aber es hat als Idee hinter unveränderlichen Datenstrukturen einen erheblichen Vorteil - wenn etwas nicht geändert wird, wird es nicht geändert. Wenn etwas geändert wird, wird es wahrscheinlich geändert . Das gibt uns einen superschnellen Vergleich, aber mit einigen falschen Negativen, wie im ersten Beispiel.
Bei der Idee geht es darum, sich auf die Daten zu "konzentrieren", von denen Sie abhängig sind
Es gibt zwei Momente, die ich hätte erwähnen sollen:
lodash.memoize
und fast-memoize
konvertieren Ihre Daten in eine Zeichenfolge, die als Schlüssel verwendet werden soll. Das bedeutet, dass sie 1) nicht schnell sind 2) nicht sicher sind 3) falsch positive Ergebnisse erzeugen können - einige unterschiedliche Daten können dieselbe Zeichenfolgendarstellung haben . Dies könnte die "Cache-Hot-Rate" verbessern, ist aber tatsächlich eine SEHR SCHLECHTE Sache.- Es gibt einen ES6-Proxy-Ansatz, bei dem alle verwendeten Variablen verfolgt und nur wichtige Schlüssel überprüft werden. Ich persönlich möchte zwar unzählige Daten-Selektoren erstellen - Sie mögen den Prozess möglicherweise nicht oder verstehen ihn nicht, möchten aber möglicherweise sofort eine ordnungsgemäße Memoisierung - und verwenden dann den Memoize-Status .
Problem 2: Es ist "eine Cache-Zeile".
Unendliche Cache-Größe ist ein Killer. Jeder unkontrollierte Cache ist ein Killer, solange der Speicher ziemlich begrenzt ist. Also - alle besten Bibliotheken sind "One-Cache-Line-Long". Das ist ein Merkmal und eine starke Designentscheidung. Ich habe gerade geschrieben, wie richtig es ist, und glauben Sie mir - es ist wirklich richtig , aber es ist immer noch ein Problem. Ein großes Problem.
const tasks = getTasks(state);
Sobald derselbe Selektor mit verschiedenen Quelldaten arbeiten muss, mit mehr als einem - ist alles kaputt. Und es ist leicht, auf das Problem zu stoßen:
- Solange wir Selektoren verwenden, um Aufgaben aus einem Status abzurufen, können wir dieselben Selektoren verwenden, um etwas aus einer Aufgabe abzurufen. Intensiv kommt von der API selbst. Aber es funktioniert nicht, dann können Sie sich nur den letzten Anruf merken, müssen aber mit mehreren Datenquellen arbeiten.
- Das gleiche Problem besteht bei mehreren Reaktionskomponenten - sie sind alle gleich und alle etwas unterschiedlich, rufen unterschiedliche Aufgaben ab und löschen die Ergebnisse voneinander.
Es gibt 3 mögliche Lösungen:

Diese Bibliothek würde Ihnen helfen, den Memo-Cache zu "behalten", aber nicht zu löschen. Vor allem, weil es 5 (FÜNF!) Verschiedene Cache-Strategien implementiert, die auf jeden Fall passen. Das ist ein schlechter Geruch. Was ist, wenn Sie die falsche wählen?
Alle Daten, die Sie gespeichert haben - Sie müssen sie früher oder später vergessen. Es geht nicht darum, sich an den letzten Funktionsaufruf zu erinnern - es geht darum, ihn zum richtigen Zeitpunkt zu VERGESSEN. Nicht zu früh und das Auswendiglernen ruinieren und nicht zu spät.
Hast du die Idee? Jetzt vergiss es! Und wo ist die 3. Variante?
Lass uns eine Pause machen
Hör auf Entspannen Sie sich Atme tief ein. Und beantworten Sie eine einfache Frage: Was ist das Ziel? Was müssen wir tun, um das Ziel zu erreichen? Was würde den Tag retten?
TIPP: Wo befindet sich dieser verdammte "Cache"?

Wo befindet sich dieser "Cache"? Ja - das ist die richtige Frage. Danke, dass du es gefragt hast. Und die Antwort ist einfach - sie befindet sich in einem Verschluss. An einer versteckten Stelle in * einer gespeicherten Funktion. Zum Beispiel - hier ist memoize-one
Code:
function(fn) { let lastArgs;
Sie erhalten einen memoizedCall
, in dem das letzte Ergebnis in der Nähe des lokalen Verschlusses gespeichert ist, auf den nur memoizedCall zugreifen kann. Ein sicherer Ort. "das" ist ein sicherer Ort.
Reselect
macht dasselbe und die einzige Möglichkeit, einen "Fork" mit einem anderen Cache zu erstellen - erstellen Sie einen neuen Memoization-Abschluss.
Aber die (andere) Hauptfrage - wann wäre es (weg) "weg"?
TLDR: Es wäre mit einer Funktion "weg", wenn die Funktionsinstanz von Garbage Collector gegessen würde.
Instanz? Instanz! Also - was ist mit Memoisierung pro Instanz? Es gibt einen ganzen Artikel darüber in der React-Dokumentation
Kurz gesagt - wenn Sie klassenbasierte Reaktionskomponenten verwenden, können Sie Folgendes tun:
import memoize from "memoize-one"; class Example extends Component { filter = memoize(
Also - wo ist "lastResult" gespeichert? Innerhalb eines lokalen Bereichs von gespeicherten Filtern innerhalb dieser Klasseninstanz. Und wann wäre es "weg"?
Diesmal wäre es mit einer Klasseninstanz "weg". Sobald die Komponente abmontiert wurde, ist sie spurlos verschwunden. Es ist eine echte "pro Instanz", und Sie können this.lastResult
, um ein zeitliches Ergebnis mit genau dem gleichen "Memoization" -Effekt zu this.lastResult
.
Was ist mit React.Hooks?
Wir kommen näher. Redux-Hooks haben einige verdächtige Befehle, bei denen es wahrscheinlich um das Auswendiglernen geht. Like - useMemo
, useCallback
, useRef

Aber die Frage - WO speichert es diesmal einen gespeicherten Wert?
Kurz gesagt - es speichert es in "Hooks" in einem speziellen Teil eines VDOM-Elements, das als Faser bekannt ist und einem aktuellen Element zugeordnet ist. Innerhalb einer parallelen Datenstruktur.
Nicht so kurze Hooks verändern die Funktionsweise Ihres Programms und verschieben Ihre Funktion in eine andere. Einige Variablen befinden sich an einer versteckten Stelle in einem übergeordneten Abschluss . Solche Funktionen sind als suspendierbare oder wiederaufnehmbare Funktionen bekannt - Coroutinen. In JavaScript werden sie normalerweise als generators
oder async functions
.
Aber das ist ein bisschen extrem. In kürzester Zeit speichert useMemo darin gespeicherte Werte. Es ist nur ein bisschen anders "dies".
Wenn wir eine bessere Memo-Bibliothek erstellen wollen, sollten wir ein besseres "dies" finden.
Zing!
WeakMaps!
Ja! WeakMaps! Zum Speichern des Schlüsselwerts, wo der Schlüssel dies wäre, solange WeakMap nichts anderes als dies akzeptiert, dh "Objekte".
Lassen Sie uns ein einfaches Beispiel erstellen:
const createHiddenSpot = (fn) => { const map = new WeakMap();
Es ist dumm einfach und ganz "richtig". Also "wann wäre es weg"?
- Vergiss schwache Auswahl und eine ganze "Karte" wäre weg
- Vergiss todos [0] und ihr schwacher Eintrag wäre weg
- vergiss todos - und gespeicherte Daten wären weg!
Es ist klar, wann etwas "weg" sein würde - nur wenn es sollte!
Magisch - alle Probleme bei der erneuten Auswahl sind verschwunden. Probleme mit aggressiven Memoisierung - auch ein Goner.
Dieser Ansatz Erinnern Sie sich an die Daten, bis es Zeit ist, zu vergessen . Es ist unglaublich, aber um sich besser an etwas zu erinnern, muss man es besser vergessen können.
Das einzige, was dauert, ist, eine robustere API für diesen Fall zu erstellen
Kashe - ist ein Cache
kashe ist eine WeakMap-basierte Memoization-Bibliothek, die Ihren Tag retten könnte.
Diese Bibliothek bietet 4 Funktionen
kashe
- zum kashe
.box
- für die vorangestellte Memoisierung, um die Wahrscheinlichkeit der Memoisierung zu erhöhen .inbox
- verschachtelte vorangestellte Memoisierung, um die Änderung der Memoisierung zu verringernfork
- (offensichtlich) Memoisierung.
kashe (fn) => memoizedFn (... args)
Es ist eigentlich ein createHiddenSpot aus einem vorherigen Beispiel. Es wird ein erstes Argument als Schlüssel für eine interne WeakMap verwendet.
const selector = (state, prop) => ({result: state[prop]}); const memoized = kashe(selector); const old = memoized(state, 'x') memoized(state, 'x') === old memoized(state, 'y') === memoized(state, 'y')
Das erste Argument ist ein Schlüssel. Wenn Sie die Funktion erneut mit demselben Schlüssel aufgerufen haben, aber unterschiedliche Argumente - der Cache würde ersetzt, ist es immer noch eine Cachezeile lange Memoisierung. Damit es funktioniert, müssen Sie verschiedene Schlüssel für verschiedene Fälle bereitstellen, wie ich es bei einem Beispiel für eine schwache Auswahl getan habe, um unterschiedliche Ergebnisse bereitzustellen, um die Ergebnisse zu speichern. Kaskaden erneut auswählen A ist immer noch das Richtige.
Nicht alle Funktionen sind kashe-memoizable. Das erste Argument muss ein Objekt, ein Array oder eine Funktion sein. Es sollte als Schlüssel für WeakMap verwendet werden können.
box (fn) => memoizedFn2 (box, ... args)
Dies ist dieselbe Funktion, die nur zweimal angewendet wird. Einmal für fn, einmal für memoizedFn, wobei den Argumenten ein führender Schlüssel hinzugefügt wird. Es könnte jede Funktion kashe-memoizable machen.
Es ist ziemlich deklarativ - hey Funktion! Ich werde die Ergebnisse in dieser Box speichern.
Wenn Sie die bereits gespeicherte Funktion aktivieren - Sie erhöhen die Memoisierungschance, wie z. B. die Memoisierung pro Instanz -, können Sie eine Memoisierungskaskade erstellen.
const selectSomethingFromTodo = (state, prop) => ... const selector = kashe(selectSomethingFromTodo); const boxedSelector = kashe(selector); class Component { render () { const result = boxedSelector(this, todos, this.props.todoId);
Posteingang (fn) => memoizedFn2 (box, ... args)
Dieser ist der Box entgegengesetzt, macht aber fast dasselbe und befiehlt dem verschachtelten Cache, Daten in der bereitgestellten Box zu speichern. Unter einem Gesichtspunkt - es verringert die Memoisierungswahrscheinlichkeit (es gibt keine Memoisierungskaskade), aber unter einem anderen Gesichtspunkt - werden die Cache-Kollisionen entfernt und Prozesse isoliert, wenn sie sich aus irgendeinem Grund nicht gegenseitig stören sollten.
Es ist ziemlich deklarativ - hey! Alle drinnen! Hier ist eine Box zur Verwendung
const getAndSet = (task, number) => task.value + number; const memoized = kashe(getAndSet); const inboxed = inbox(getAndSet); const doubleBoxed = inbox(memoized); memoized(task, 1)
Gabel (kashe-memoized) => kashe-memoized
Fork ist eine echte Fork - sie erhält jede kashe-gespeicherte Funktion und gibt dieselbe zurück, jedoch mit einem anderen internen Cache-Eintrag. Erinnern Sie sich an die Factory-Methode von redux mapStateToProps?
const mapStateToProps = () => {
Neu auswählen
Und noch etwas sollten Sie wissen - Kashe könnte Reselect ersetzen. Im wahrsten Sinne des Wortes.
import { createSelector } from 'kashe/reselect';
Es ist eigentlich die gleiche Neuauswahl, die nur mit Kashe als Memo-Funktion erstellt wurde.
Codesandbox
Hier ist ein kleines Beispiel zum Spielen. Sie können auch Tests überprüfen - sie sind kompakt und solide.
Wenn Sie mehr über Caching und Memoisierung erfahren möchten, lesen Sie, wie ich vor einem Jahr die schnellste Memoisierungsbibliothek geschrieben habe.
PS: Es ist erwähnenswert, dass die einfachere Version dieses Ansatzes - schwaches Auswendiglernen - für eine Weile in Emotionen verwendet wird. Keine Beschwerden. nano-memoize verwendet WeakMaps auch für einen einzelnen Argumentationsfall.
Verstanden? Ein "schwächer" Ansatz würde Ihnen helfen, sich besser an etwas zu erinnern und es besser zu vergessen.
https://github.com/theKashey/kashe
Ja, um etwas zu vergessen - könnten Sie bitte hier schauen?
