Sådan bruges Memoize til at cache JavaScript-funktionsresultater og fremskynde din kode

Funktioner er en integreret del af programmeringen. De hjælper med at tilføje modularitet og genanvendelighed til vores kode.

Det er ret almindeligt at opdele vores program i klumper ved hjælp af funktioner, som vi senere kan ringe til for at udføre nogle nyttige handlinger.

Nogle gange kan en funktion blive dyrt at ringe til flere gange (f.eks. En funktion til beregning af et nummer). Men der er en måde, hvorpå vi kan optimere sådanne funktioner og få dem til at udføre meget hurtigere: cache .

Lad os for eksempel sige, at vi har en functiontil at returnere et nummer til et nummer:

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

Godt, lad os nu finde det factorial(50). Computeren udfører beregninger og returnerer os det endelige svar, søde!

Lad os finde, når det er gjort factorial(51). Computeren udfører igen et antal beregninger og får os resultatet, men du har måske bemærket, at vi allerede gentager et antal trin, der kunne have været undgået. En optimeret måde ville være:

factorial(51) = factorial(50) * 51

Men vores functionudfører beregningerne fra bunden, hver gang det kaldes:

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

Ville det ikke være sejt, hvis vores factorialfunktion på en eller anden måde kunne huske værdierne fra dens tidligere beregninger og bruge dem til at fremskynde udførelsen?

In kommer memoization , en måde vi functionkan huske (cache) resultaterne på. Nu hvor du har en grundlæggende forståelse af, hvad vi prøver at opnå, er her en formel definition:

Memoization er en optimeringsteknik, der primært bruges til at fremskynde computerprogrammer ved at gemme resultaterne af dyre funktionsopkald og returnere det cachelagrede resultat, når de samme input igen forekommer

At huske i enkle vendinger betyder at huske eller gemme i hukommelsen. En memoized-funktion er normalt hurtigere, for hvis funktionen kaldes efterfølgende med de (n) tidligere værdi (er), ville vi i stedet for at udføre funktionen hente resultatet fra cachen.

Her er hvordan en simpel memo-funktion kan se ud (og her er en CodePen, hvis du vil interagere med den) :

// 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 takeaways

Nogle takeaways fra ovenstående kode er:

  • memoizedAddreturnerer en, functionsom påberåbes senere. Dette er muligt, fordi funktioner i JavaScript er førsteklasses objekter, der lader os bruge dem som højere ordensfunktioner og returnere en anden funktion.
  • cachekan huske dens værdier, da den returnerede funktion har en lukning over sig.
  • Det er vigtigt, at den memoiserede funktion er ren. En ren funktion returnerer det samme output for et bestemt input, uanset hvor mange gange det kaldes, hvilket gør cachearbejdet som forventet.

Skriv din egen memoizefunktion

Den forrige kode fungerer fint, men hvad hvis vi ville gøre en funktion til en memo-funktion?

Sådan skriver du din egen 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

Nu er det godt! Denne enkle memoizefunktion vil pakke enhver simpel functionind i en memo-ækvivalent. Koden fungerer fint til enkle funktioner, og den kan let finjusteres for at håndtere et hvilket argumentssom helst antal efter dine behov. Et andet alternativ er at gøre brug af nogle de facto biblioteker som:

  • Lodash's _.memoize(func, [resolver])
  • ES7 @memoizedekoratører fra decko

At huske rekursive funktioner

Hvis du prøver at videregive en rekursiv funktion til memoizefunktionen ovenfor eller _.memoizefra Lodash, vil resultaterne ikke være som forventet, da den rekursive funktion på dens efterfølgende opkald ender med at kalde sig selv i stedet for den memoiserede funktion og derved ikke gør brug af cache.

Bare sørg for, at din rekursive funktion kalder den memoized-funktion. Her er hvordan du kan tilpasse et eksempel på en lærebog til et fakultet (codepen):

// 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

Et par punkter at bemærke fra denne kode:

  • Den factorialfunktion er rekursivt kalde en memoized udgave af sig selv.
  • Den memoiserede funktion cachelagrer værdierne fra tidligere fakturaer, hvilket forbedrer beregningerne betydeligt, da de kan genbruges factorial(6) = 6 * factorial(5)

Er memoization det samme som caching?

Ja, slags. Memoization er faktisk en bestemt type caching. Mens caching generelt kan henvise til enhver lagringsteknik (som HTTP-caching) til fremtidig brug, indebærer memoizing specifikt caching af returværdierne for a function.

Hvornår skal du huske dine funktioner

Selvom det kan se ud som om memoization kan bruges med alle funktioner, har det faktisk begrænsede brugssager:

  • For at huske en funktion skal den være ren, så returværdierne er de samme for de samme indgange hver gang
  • Memoizing er en afvejning mellem ekstra plads og ekstra hastighed og er derfor kun signifikant for funktioner, der har et begrænset inputområde, så cachelagrede værdier kan bruges oftere
  • Det kan se ud som om du skal huske dine API-opkald, men det er ikke nødvendigt, fordi browseren automatisk cachelagrer dem for dig. Se HTTP-cache for flere detaljer
  • Den bedste brugssag, jeg fandt til memoiserede funktioner, er for tunge beregningsfunktioner, som kan forbedre ydeevnen markant (faktoriel og retracement er ikke rigtig gode eksempler i den virkelige verden)
  • Hvis du er interesseret i React / Redux, kan du tjekke genvalg ud, der bruger en memoized-vælger for at sikre, at beregninger kun sker, når der sker en ændring i en relateret del af tilstandstræet.

Yderligere læsning

Følgende links kan være nyttige, hvis du vil vide mere om nogle af emnerne fra denne artikel mere detaljeret:

  • Funktioner med højere ordre i JavaScript
  • Lukninger i JavaScript
  • Rene funktioner
  • Lodashs _.memoizedokumenter og kildekode
  • Flere eksempler på memoisering her og her
  • reactjs / genvælg

Jeg håber, at denne artikel var nyttig for dig, og du har fået en bedre forståelse af memoization i JavaScript :)

Du kan følge mig på twitter for at få de seneste opdateringer. Jeg er også begyndt at sende nyere indlæg på min personlige blog.