Funcțiile fac parte integrantă din programare. Ele ajută la adăugare modularitate și reutilizare la codul nostru.

Este destul de obișnuit să împărțim programul nostru în bucăți folosind funcții pe care le putem numi mai târziu pentru a efectua o acțiune utilă.

Uneori, o funcție poate deveni scumpă pentru a apela de mai multe ori (de exemplu, o funcție pentru a calcula factorial a unui număr). Dar există o modalitate de a optimiza astfel de funcții și de a le face să se execute mult mai repede: stocarea în cache.

De exemplu, să presupunem că avem un function pentru a returna factorialul unui număr:

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

Super, acum să găsim factorial(50). Calculatorul va efectua calcule și ne va întoarce răspunsul final, dulce!

Când se termină, să găsim factorial(51). Computerul efectuează din nou o serie de calcule și ne obține rezultatul, dar este posibil să fi observat că repetăm ​​deja o serie de pași care ar fi putut fi evitați. O modalitate optimizată ar fi:

factorial(51) = factorial(50) * 51

Dar a noastră function efectuează calculele de la zero de fiecare dată când se numește:

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

Nu ar fi grozav dacă cumva al nostru factorial funcția ar putea să-și amintească valorile din calculele sale anterioare și să le folosească pentru a accelera execuția?

Intră memoizare, o cale pentru noi function pentru a aminti (cache) rezultatele. Acum că aveți o înțelegere de bază a ceea ce încercăm să realizăm, iată o definiție formală:

Memorizare este o tehnică de optimizare utilizată în primul rând pentru a accelera programele de calculator de către stocarea rezultatelor apelurilor funcționale costisitoare și returnarea rezultatului cache atunci când aceleași intrări apar din nou

Memorizare în termeni simpli înseamnă memorând sau stocarea în memorie. O funcție memorată este de obicei mai rapidă, deoarece dacă funcția este apelată ulterior cu valoarea / valorile anterioare, atunci în loc să executăm funcția, vom prelua rezultatul din cache.

Iată cum ar putea arăta o funcție simplă memorată (și iată un CodePen în cazul în care doriți să interacționați cu el):

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

Câteva opțiuni de plătit din codul de mai sus sunt:

  • memoizedAdd returnează un function care este invocat mai târziu. Acest lucru este posibil, deoarece în JavaScript, funcțiile sunt obiecte de primă clasă, ceea ce ne permite să le folosim ca funcții de ordin superior și returnează o altă funcție.
  • cache își poate aminti valori deoarece funcția returnată are un închidere peste ea.
  • Este esențial ca funcția memoized să fie pur. O funcție pură va returna aceeași ieșire pentru o anumită intrare, indiferent de câte ori este apelată, ceea ce face ca cache lucrează conform așteptărilor.

Scriind-o pe a ta memoize funcţie

Codul anterior funcționează bine, dar dacă am dori să transformăm orice funcție într-o funcție memoized?

Iată cum să scrieți propria funcție de memorare (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

Acum e minunat! Acest simplu memoize funcția va înfășura orice simplu function într-un echivalent memoizat. Codul funcționează bine pentru funcții simple și poate fi modificat cu ușurință pentru a gestiona orice număr de arguments conform nevoilor dumneavoastră. O altă alternativă este de a utiliza unele biblioteci de facto, cum ar fi:

Memorizarea funcțiilor recursive

Dacă încercați să treceți o funcție recursivă la memoize funcția de mai sus sau _.memoize de la Lodash, rezultatele nu vor fi așa cum era de așteptat, deoarece funcția recursivă la apelurile sale ulterioare va ajunge să se apeleze singură în loc de funcția memoized, neutilizând astfel cache.

Asigurați-vă că funcția recursivă apelează funcția memoized. Iată cum puteți modifica un manual factorial exemplu (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

Câteva puncte de reținut din acest cod:

  • factorial funcția apelează recursiv o versiune memoizată a sa.
  • Funcția memorată memorează în cache valorile factorialelor anterioare, ceea ce îmbunătățește semnificativ calculele, deoarece acestea pot fi refolosite factorial(6) = 6 * factorial(5)

Memorizarea este aceeași cu memorarea în cache?

Da, cam. Memorizarea este de fapt un tip specific de cache. În timp ce cache-ul se poate referi în general la orice tehnică de stocare (cum ar fi Cache HTTP) pentru utilizare viitoare, memoizing implică în mod specific stocarea în cache valorile returnate ale unui function.

Când să vă memorați funcțiile

Deși ar putea părea că memoizarea poate fi utilizată cu toate funcțiile, are de fapt cazuri de utilizare limitate:

  • Pentru a memora o funcție, aceasta ar trebui să fie pură, astfel încât valorile returnate să fie aceleași pentru aceleași intrări de fiecare dată
  • Memorizarea este un compromis între spațiul adăugat și viteza adăugată și, prin urmare, este semnificativ doar pentru funcțiile cu un interval de intrare limitat, astfel încât valorile din cache să poată fi utilizate mai frecvent
  • S-ar putea să arătați că ar trebui să memorați apelurile API, însă nu este necesar, deoarece browserul le memorează automat în cache. Vedea Cache HTTP pentru mai multe detalii
  • Cel mai bun caz de utilizare pe care l-am găsit pentru funcțiile memorate este pentru funcții de calcul grele care poate îmbunătăți semnificativ performanța (factorial și Fibonacci nu sunt exemple bune din lumea reală)
  • Dacă sunteți în React / Redux puteți verifica reselectați care folosește un selector memoized pentru a vă asigura că calculele se întâmplă numai atunci când se produce o modificare într-o parte legată a arborelui de stare.

Lecturi suplimentare

Următoarele linkuri pot fi utile dacă doriți să aflați mai multe despre unele dintre subiectele din acest articol mai detaliat:

Sper că acest articol a fost util pentru dvs. și ați câștigat o mai bună înțelegere a memoriei în JavaScript 🙂


Poți să mă urmărești mai departe stare de nervozitate pentru ultimele actualizări. De asemenea, am început să postez postări mai recente pe pagina mea personală blog.